From b222701169762f52095989c3f44ad2a7afb801a5 Mon Sep 17 00:00:00 2001 From: Matthew Zagaja Date: Tue, 7 Nov 2023 20:32:56 -0500 Subject: [PATCH] Revert "Merge pull request #489 from thiagobardini/public-view-langPref-localization" This reverts the backend and .github changes in commit 42a39eb2e9e3d0b39932580acfea57893b8b4939, reversing changes made to 39be7c17ce987bd68904d55b723c5922af64aa48. --- .github/workflows/deploy.yml | 29 +++ .github/workflows/openshift.yml | 213 ++++++++++++++++++ .github/workflows/test.yml | 35 --- backend/Gemfile | 4 +- backend/Gemfile.lock | 180 ++++++++------- backend/app/controllers/surveys_controller.rb | 12 +- backend/app/jobs/canonicalize_address_job.rb | 33 ++- .../app/models/localized_survey_question.rb | 5 + backend/app/models/survey_question.rb | 18 ++ .../assignments/_assignment.json.jbuilder | 7 +- backend/app/views/homes/_home.json.jbuilder | 1 + .../app/views/surveys/_survey.json.jbuilder | 10 +- backend/app/views/surveys/index.json.jbuilder | 7 +- ...00437_create_localized_survey_questions.rb | 19 ++ backend/db/schema.rb | 19 +- backend/db/seed_importer.rb | 13 +- .../jobs/canonicalize_address_job_spec.rb | 7 +- 17 files changed, 464 insertions(+), 148 deletions(-) create mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/openshift.yml create mode 100644 backend/app/models/localized_survey_question.rb create mode 100644 backend/db/migrate/20230920000437_create_localized_survey_questions.rb diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..bf9e40c0 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,29 @@ +name: Deploy + +on: + # https://docs.github.com/en/actions/reference/events-that-trigger-workflows + release: + types: [published] + +env: + REACT_APP_PUBLIC_SURVEY_ENABLED: 'false' + REACT_APP_API_URL: https://api.bostonhpa.org + REACT_APP_RECAPTCHA_KEY: 6LdHAxYmAAAAAHGN0eNzJhGpCrxm7FisXyZoy8cr + +jobs: + deploy-server: + uses: ./.github/workflows/openshift.yml + with: + app: bhpa-backend + context: backend + port: 3000 + deploy-website: + uses: ./.github/workflows/openshift.yml + with: + app: bhpa-frontend + context: frontend/front + port: 8080 + build-args: | + REACT_APP_RECAPTCHA_KEY=$REACT_APP_RECAPTCHA_KEY + REACT_APP_PUBLIC_SURVEY_ENABLED=$REACT_APP_PUBLIC_SURVEY_ENABLED + REACT_APP_API_URL=$REACT_APP_API_URL diff --git a/.github/workflows/openshift.yml b/.github/workflows/openshift.yml new file mode 100644 index 00000000..795899a5 --- /dev/null +++ b/.github/workflows/openshift.yml @@ -0,0 +1,213 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# 💁 The OpenShift Starter workflow will: +# - Checkout your repository +# - Perform a container image build +# - Push the built image to the GitHub Container Registry (GHCR) +# - Log in to your OpenShift cluster +# - Create an OpenShift app from the image and expose it to the internet + +# ℹī¸ Configure your repository and the workflow with the following steps: +# 1. Have access to NERC's OpenShift cluster Refer to https://nerc-project.github.io/nerc-docs/openshift/logging-in/access-the-openshift-web-console/. To get access NERC's OCP web console you need to be part of ColdFront's active allocation as described [here](https://nerc-project.github.io/nerc-docs/get-started/get-an-allocation/#request-a-new-openshift-resource-allocation-for-openshift-project). +# 2. Create the OPENSHIFT_SERVER, OPENSHIFT_TOKEN and IMAGE_REGISTRY_PASSWORD repository secrets. Refer to: +# - https://github.com/redhat-actions/oc-login#readme +# - https://docs.github.com/en/actions/reference/encrypted-secrets +# - https://cli.github.com/manual/gh_secret_set +# 3. (Optional) Edit the top-level 'env' section as marked with '🖊ī¸' if the defaults are not suitable for your project. +# 4. (Optional) Edit the build-image step to build your project. +# The default build type is by using a Dockerfile at the root of the repository, +# but can be replaced with a different file, a source-to-image build, or a step-by-step buildah build. +# 5. Commit and push the workflow file to your default branch to trigger a workflow run. + +# 👋 Visit our GitHub organization at https://github.com/redhat-actions/ to see our actions and provide feedback. + +name: OpenShift + +env: + # 🖊ī¸ EDIT your repository secrets to log into your OpenShift cluster and set up the context. + # See https://github.com/redhat-actions/oc-login#readme for how to retrieve these values. + # To get a permanent token, refer to https://github.com/redhat-actions/oc-login/wiki/Using-a-Service-Account-for-GitHub-Actions + OPENSHIFT_SERVER: ${{ secrets.OPENSHIFT_SERVER }} + OPENSHIFT_TOKEN: ${{ secrets.OPENSHIFT_TOKEN }} + # 🖊ī¸ EDIT to set the kube context's namespace, you can view your available project namespaces by login into: https://console.apps.shift.nerc.mghpcc.org. + OPENSHIFT_NAMESPACE: 'boston-heat-pump-accelerator' + + # EDIT to set a name for your OpenShift app, or a default one will be generated below. + APP_NAME: ${{ inputs.app }} + + # EDIT with the port your application should be accessible on. + # If the container image exposes *exactly one* port, this can be left blank. + # Refer to the 'port' input of https://github.com/redhat-actions/oc-new-app + APP_PORT: ${{ inputs.port }} + + # EDIT to change the image registry settings. + # Registries such as GHCR, Quay.io, and Docker Hub are supported. + IMAGE_REGISTRY: ghcr.io/${{ github.actor }} + # EDIT with your registry username. + IMAGE_REGISTRY_USER: ${{ github.actor }} + IMAGE_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + + # EDIT to specify custom tags for the container image, or default tags will be generated below. + IMAGE_TAGS: '' + +on: + workflow_call: + inputs: + app: + type: string + required: true + port: + type: number + required: true + context: + type: string + required: true + build-args: + type: string + required: false + +jobs: + # 🖊ī¸ EDIT if you want to run vulnerability check on your project before deploying + # the application. Please uncomment the below CRDA scan job and configure to run it in + # your workflow. For details about CRDA action visit https://github.com/redhat-actions/crda/blob/main/README.md + # + # TODO: Make sure to add 'CRDA Scan' starter workflow from the 'Actions' tab. + # For guide on adding new starter workflow visit https://docs.github.com/en/github-ae@latest/actions/using-workflows/using-starter-workflows + + # crda-scan: + # uses: ./.github/workflows/crda.yml + # secrets: + # CRDA_KEY: ${{ secrets.CRDA_KEY }} + # # SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} # Either use SNYK_TOKEN or CRDA_KEY + + openshift-ci-cd: + # 🖊ī¸ Uncomment this if you are using CRDA scan step above + # needs: crda-scan + name: Build and deploy ${{ inputs.app }} to OpenShift + runs-on: ubuntu-20.04 + environment: production + + outputs: + ROUTE: ${{ steps.deploy-and-expose.outputs.route }} + SELECTOR: ${{ steps.deploy-and-expose.outputs.selector }} + + steps: + - name: Check for required secrets + uses: actions/github-script@v6 + with: + script: | + const secrets = { + OPENSHIFT_SERVER: `${{ secrets.OPENSHIFT_SERVER }}`, + OPENSHIFT_TOKEN: `${{ secrets.OPENSHIFT_TOKEN }}`, + }; + + const GHCR = "ghcr.io"; + if (`${{ env.IMAGE_REGISTRY }}`.startsWith(GHCR)) { + core.info(`Image registry is ${GHCR} - no registry password required`); + } + else { + core.info("A registry password is required"); + secrets["IMAGE_REGISTRY_PASSWORD"] = `${{ secrets.IMAGE_REGISTRY_PASSWORD }}`; + } + + const missingSecrets = Object.entries(secrets).filter(([ name, value ]) => { + if (value.length === 0) { + core.error(`Secret "${name}" is not set`); + return true; + } + core.info(`✔ī¸ Secret "${name}" is set`); + return false; + }); + + if (missingSecrets.length > 0) { + core.setFailed(`❌ At least one required secret is not set in the repository. \n` + + "You can add it using:\n" + + "GitHub UI: https://docs.github.com/en/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository \n" + + "GitHub CLI: https://cli.github.com/manual/gh_secret_set \n" + + "Also, refer to https://github.com/redhat-actions/oc-login#getting-started-with-the-action-or-see-example"); + } + else { + core.info(`✅ All the required secrets are set`); + } + + - name: Check out repository + uses: actions/checkout@v3 + + - name: Determine app name + if: env.APP_NAME == '' + run: | + echo "APP_NAME=$(basename $PWD)" | tee -a $GITHUB_ENV + + - name: Determine image tags + if: env.IMAGE_TAGS == '' + run: | + echo "IMAGE_TAGS=latest ${GITHUB_SHA::12}" | tee -a $GITHUB_ENV + + # https://github.com/redhat-actions/buildah-build#readme + - name: Build from Dockerfile + id: build-image + uses: redhat-actions/buildah-build@v2 + with: + image: ${{ env.APP_NAME }} + tags: ${{ env.IMAGE_TAGS }} + build-args: ${{ inputs.build-args }} + + # If you don't have a Dockerfile/Containerfile, refer to https://github.com/redhat-actions/buildah-build#scratch-build-inputs + # Or, perform a source-to-image build using https://github.com/redhat-actions/s2i-build + # Otherwise, point this to your Dockerfile/Containerfile relative to the repository root. + dockerfiles: | + ./${{ inputs.context }}/Dockerfile + + # https://github.com/redhat-actions/push-to-registry#readme + - name: Push to registry + id: push-image + uses: redhat-actions/push-to-registry@v2 + with: + image: ${{ steps.build-image.outputs.image }} + tags: ${{ steps.build-image.outputs.tags }} + registry: ${{ env.IMAGE_REGISTRY }} + username: ${{ env.IMAGE_REGISTRY_USER }} + password: ${{ env.IMAGE_REGISTRY_PASSWORD }} + + # The path the image was pushed to is now stored in ${{ steps.push-image.outputs.registry-path }} + + - name: Install oc + uses: redhat-actions/openshift-tools-installer@v1 + with: + oc: 4 + + # https://github.com/redhat-actions/oc-login#readme + - name: Log in to OpenShift + uses: redhat-actions/oc-login@v1 + with: + openshift_server_url: ${{ env.OPENSHIFT_SERVER }} + openshift_token: ${{ env.OPENSHIFT_TOKEN }} + insecure_skip_tls_verify: true + namespace: ${{ env.OPENSHIFT_NAMESPACE }} + + # This step should create a deployment, service, and route to run your app and expose it to the internet. + # https://github.com/redhat-actions/oc-new-app#readme + - name: Create and expose app + id: deploy-and-expose + uses: redhat-actions/oc-new-app@v1 + with: + app_name: ${{ env.APP_NAME }} + image: ${{ steps.push-image.outputs.registry-path }} + namespace: ${{ env.OPENSHIFT_NAMESPACE }} + port: ${{ env.APP_PORT }} + + - name: Print application URL + env: + ROUTE: ${{ steps.deploy-and-expose.outputs.route }} + SELECTOR: ${{ steps.deploy-and-expose.outputs.selector }} + run: | + [[ -n ${{ env.ROUTE }} ]] || (echo "Determining application route failed in previous step"; exit 1) + echo + echo "======================== Your application is available at: ========================" + echo ${{ env.ROUTE }} + echo "===================================================================================" + echo + echo "Your app can be taken down with: \"oc delete all --selector='${{ env.SELECTOR }}'\"" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3e993cf4..841c000e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -72,38 +72,3 @@ jobs: run: yarn test-style - name: Run tests run: yarn test - - build_image: - name: Build Image - runs-on: ubuntu-latest - defaults: - run: - working-directory: . - needs: [test-backend] - steps: - - name: Check out code - uses: actions/checkout@v3 - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: us-east-1 - - name: Login to Amazon ECR - id: login-ecr - uses: aws-actions/amazon-ecr-login@v1 - - # Add support for more platforms with QEMU (optional) - # https://github.com/docker/setup-qemu-action - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - with: - platforms: linux/arm64 - - name: Build, tag, and push image to Amazon ECR - env: - ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} - ECR_REPOSITORY: urban-league-heat-pump-accelerator - IMAGE_TAG: ${{ github.sha }} - run: | - docker buildx build --platform linux/arm64 -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f ./backend/Dockerfile . --push diff --git a/backend/Gemfile b/backend/Gemfile index 667a564f..469d236e 100644 --- a/backend/Gemfile +++ b/backend/Gemfile @@ -9,12 +9,14 @@ gem 'bootsnap', require: false gem 'devise' gem 'devise-jwt' gem 'faraday' +gem 'http_accept_language' gem 'jbuilder' gem 'newrelic_rpm' gem 'pg', '~> 1.1' gem 'puma', '~> 5.0' +gem 'rack', '~> 2.0' gem 'rack-cors', '~> 2.0' -gem 'rails', '~> 7.0.3', '>= 7.0.3.1' +gem 'rails', '>= 7.0' gem 'sprockets-rails' gem 'sucker_punch', '~> 3.0' gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] diff --git a/backend/Gemfile.lock b/backend/Gemfile.lock index b0f3317f..a0c479ff 100644 --- a/backend/Gemfile.lock +++ b/backend/Gemfile.lock @@ -1,74 +1,83 @@ GEM remote: https://rubygems.org/ specs: - actioncable (7.0.8) - actionpack (= 7.0.8) - activesupport (= 7.0.8) + actioncable (7.1.0) + actionpack (= 7.1.0) + activesupport (= 7.1.0) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.8) - actionpack (= 7.0.8) - activejob (= 7.0.8) - activerecord (= 7.0.8) - activestorage (= 7.0.8) - activesupport (= 7.0.8) + zeitwerk (~> 2.6) + actionmailbox (7.1.0) + actionpack (= 7.1.0) + activejob (= 7.1.0) + activerecord (= 7.1.0) + activestorage (= 7.1.0) + activesupport (= 7.1.0) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.8) - actionpack (= 7.0.8) - actionview (= 7.0.8) - activejob (= 7.0.8) - activesupport (= 7.0.8) + actionmailer (7.1.0) + actionpack (= 7.1.0) + actionview (= 7.1.0) + activejob (= 7.1.0) + activesupport (= 7.1.0) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp - rails-dom-testing (~> 2.0) - actionpack (7.0.8) - actionview (= 7.0.8) - activesupport (= 7.0.8) - rack (~> 2.0, >= 2.2.4) + rails-dom-testing (~> 2.2) + actionpack (7.1.0) + actionview (= 7.1.0) + activesupport (= 7.1.0) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.8) - actionpack (= 7.0.8) - activerecord (= 7.0.8) - activestorage (= 7.0.8) - activesupport (= 7.0.8) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + actiontext (7.1.0) + actionpack (= 7.1.0) + activerecord (= 7.1.0) + activestorage (= 7.1.0) + activesupport (= 7.1.0) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.8) - activesupport (= 7.0.8) + actionview (7.1.0) + activesupport (= 7.1.0) builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (7.0.8) - activesupport (= 7.0.8) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (7.1.0) + activesupport (= 7.1.0) globalid (>= 0.3.6) - activemodel (7.0.8) - activesupport (= 7.0.8) - activerecord (7.0.8) - activemodel (= 7.0.8) - activesupport (= 7.0.8) - activestorage (7.0.8) - actionpack (= 7.0.8) - activejob (= 7.0.8) - activerecord (= 7.0.8) - activesupport (= 7.0.8) + activemodel (7.1.0) + activesupport (= 7.1.0) + activerecord (7.1.0) + activemodel (= 7.1.0) + activesupport (= 7.1.0) + timeout (>= 0.4.0) + activestorage (7.1.0) + actionpack (= 7.1.0) + activejob (= 7.1.0) + activerecord (= 7.1.0) + activesupport (= 7.1.0) marcel (~> 1.0) - mini_mime (>= 1.1.0) - activesupport (7.0.8) + activesupport (7.1.0) + base64 + bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) minitest (>= 5.1) + mutex_m tzinfo (~> 2.0) ast (2.4.2) base64 (0.1.1) bcrypt (3.1.19) + bigdecimal (3.1.4) bindex (0.8.1) bootsnap (1.16.0) msgpack (~> 1.2) @@ -80,6 +89,7 @@ GEM bundler (>= 1.2.0, < 3) thor (~> 1.0) concurrent-ruby (1.2.2) + connection_pool (2.4.1) crass (1.0.6) date (3.3.3) debug (1.8.0) @@ -95,6 +105,8 @@ GEM devise (~> 4.0) warden-jwt_auth (~> 0.8) diff-lcs (1.5.0) + drb (2.1.1) + ruby2_keywords dry-auto_inject (1.0.1) dry-core (~> 1.0) zeitwerk (~> 2.6) @@ -117,6 +129,7 @@ GEM faraday-net_http (3.0.2) globalid (1.2.1) activesupport (>= 6.1) + http_accept_language (2.1.1) i18n (1.14.1) concurrent-ruby (~> 1.0) io-console (0.6.0) @@ -129,7 +142,7 @@ GEM json (2.6.3) jwt (2.7.1) language_server-protocol (3.17.0.3) - loofah (2.21.3) + loofah (2.21.4) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -138,11 +151,11 @@ GEM net-pop net-smtp marcel (1.0.2) - method_source (1.0.0) mini_mime (1.1.5) minitest (5.20.0) msgpack (1.7.2) - net-imap (0.3.7) + mutex_m (0.1.2) + net-imap (0.4.1) date net-protocol net-pop (0.1.2) @@ -153,15 +166,11 @@ GEM net-protocol newrelic_rpm (9.5.0) nio4r (2.5.9) - nokogiri (1.15.4-arm64-darwin) - racc (~> 1.4) - nokogiri (1.15.4-x86_64-darwin) - racc (~> 1.4) nokogiri (1.15.4-x86_64-linux) racc (~> 1.4) orm_adapter (0.5.0) parallel (1.23.0) - parser (3.2.2.3) + parser (3.2.2.4) ast (~> 2.4.1) racc pg (1.5.4) @@ -173,22 +182,27 @@ GEM rack (2.2.8) rack-cors (2.0.1) rack (>= 2.0.0) + rack-session (1.0.1) + rack (< 3) rack-test (2.1.0) rack (>= 1.3) - rails (7.0.8) - actioncable (= 7.0.8) - actionmailbox (= 7.0.8) - actionmailer (= 7.0.8) - actionpack (= 7.0.8) - actiontext (= 7.0.8) - actionview (= 7.0.8) - activejob (= 7.0.8) - activemodel (= 7.0.8) - activerecord (= 7.0.8) - activestorage (= 7.0.8) - activesupport (= 7.0.8) + rackup (1.0.0) + rack (< 3) + webrick + rails (7.1.0) + actioncable (= 7.1.0) + actionmailbox (= 7.1.0) + actionmailer (= 7.1.0) + actionpack (= 7.1.0) + actiontext (= 7.1.0) + actionview (= 7.1.0) + activejob (= 7.1.0) + activemodel (= 7.1.0) + activerecord (= 7.1.0) + activestorage (= 7.1.0) + activesupport (= 7.1.0) bundler (>= 1.15.0) - railties (= 7.0.8) + railties (= 7.1.0) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest @@ -196,19 +210,20 @@ GEM rails-html-sanitizer (1.6.0) loofah (~> 2.21) nokogiri (~> 1.14) - railties (7.0.8) - actionpack (= 7.0.8) - activesupport (= 7.0.8) - method_source + railties (7.1.0) + actionpack (= 7.1.0) + activesupport (= 7.1.0) + irb + rackup (>= 1.0.0) rake (>= 12.2) - thor (~> 1.0) - zeitwerk (~> 2.5) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.0.6) rdoc (6.5.0) psych (>= 4.0.0) - regexp_parser (2.8.1) - reline (0.3.8) + regexp_parser (2.8.2) + reline (0.3.9) io-console (~> 0.5) responders (3.1.0) actionpack (>= 5.2) @@ -231,7 +246,7 @@ GEM rspec-mocks (~> 3.12) rspec-support (~> 3.12) rspec-support (3.12.1) - rubocop (1.56.3) + rubocop (1.56.4) base64 (~> 0.1.1) json (~> 2.3) language_server-protocol (>= 3.17.0) @@ -245,7 +260,7 @@ GEM unicode-display_width (>= 2.4.0, < 3.0) rubocop-ast (1.29.0) parser (>= 3.2.1.0) - rubocop-rails (2.21.1) + rubocop-rails (2.21.2) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.33.0, < 2.0) @@ -267,7 +282,7 @@ GEM timeout (0.4.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unicode-display_width (2.4.2) + unicode-display_width (2.5.0) warden (1.2.9) rack (>= 2.0.9) warden-jwt_auth (0.8.0) @@ -280,14 +295,13 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) + webrick (1.8.1) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) zeitwerk (2.6.12) PLATFORMS - arm64-darwin-21 - universal-darwin-22 x86_64-linux DEPENDENCIES @@ -299,12 +313,14 @@ DEPENDENCIES devise-jwt factory_bot_rails faraday + http_accept_language jbuilder newrelic_rpm pg (~> 1.1) puma (~> 5.0) + rack (~> 2.0) rack-cors (~> 2.0) - rails (~> 7.0.3, >= 7.0.3.1) + rails (>= 7.0) rspec-rails (~> 6.0.0) rubocop rubocop-rails @@ -316,7 +332,7 @@ DEPENDENCIES web-console RUBY VERSION - ruby 3.1.3p185 + ruby 3.1.4p223 BUNDLED WITH - 2.3.24 + 2.3.26 diff --git a/backend/app/controllers/surveys_controller.rb b/backend/app/controllers/surveys_controller.rb index 59dbec4e..4b63b139 100644 --- a/backend/app/controllers/surveys_controller.rb +++ b/backend/app/controllers/surveys_controller.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class SurveysController < ApplicationController + AVAILABLE_LANGUAGES = %w[en es fr-ht pt-br].freeze before_action :set_survey, only: %i[show edit update destroy] # GET /surveys or /surveys.json @@ -9,7 +10,16 @@ def index end # GET /surveys/1 or /surveys/1.json - def show; end + def show + @language_code = (params[:langPref].presence || http_accept_language.preferred_language_from(AVAILABLE_LANGUAGES)) + + # If first question of survey doesn't have this localization, then throw exception + # (No need to check all questions for this localization) + return unless @survey.survey_questions.first.localized_survey_questions.find_by(language_code: @language_code).nil? + + raise StandardError, + "Survey question #{@survey.id} does not have localization '#{@language_code}'" + end # GET /surveys/new def new diff --git a/backend/app/jobs/canonicalize_address_job.rb b/backend/app/jobs/canonicalize_address_job.rb index 5761f320..269d897b 100644 --- a/backend/app/jobs/canonicalize_address_job.rb +++ b/backend/app/jobs/canonicalize_address_job.rb @@ -28,22 +28,33 @@ def perform(home_id) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metr 'country': 'USA', 'postalcode': home_map[:zip_code] }) - result = JSON.parse(response.body)['features'] + # TODO: handle response failing + results = JSON.parse(response.body)['features'] - exact_match = result.select do |feature| - feature['properties']['housenumber'] == home_map['street_number'] && - feature['properties']['street'] == home_map['street_name'] - end - match_to_use = exact_match.empty? ? result.first : exact_match.first + # rubocop:disable Style/MultilineBlockChain + match_to_use = if (match = results.select { |r| r['properties']['match_type'] == 'exact' }.first) + # Try taking first 'exact' match + match['properties'] + elsif (match = results.select do |r| + r['properties']['match_type'] == 'interpolated' + end.max_by { |r| r['properties']['confidence'].to_f }) + # Then fall back to highest confidence 'interpolated' result + match['properties'] + end + # If couldn't find any match, match_to_use is nil + # rubocop:enable Style/MultilineBlockChain if match_to_use + # If we got some result back from Pelias, figure out what needs changing i.e. build change_map + # Compare match_to_use against home_map change_map = {} - if change_map[:street_number] != match_to_use['housenumber'].to_s + + if home_map[:street_number] != match_to_use['housenumber'].to_s change_map[:street_number] = match_to_use['housenumber'].to_s end - change_map[:city] = match_to_use['locality'] if change_map[:city] != match_to_use['locality'] - change_map[:state] = match_to_use['region'] if change_map[:state] != match_to_use['region'] - change_map[:zip_code] = match_to_use['postalcode'] if change_map[:zip_code] != match_to_use['postalcode'] + change_map[:city] = match_to_use['locality'] if home_map[:city] != match_to_use['locality'] + change_map[:state] = match_to_use['region'] if home_map[:state] != match_to_use['region'] + change_map[:zip_code] = match_to_use['postalcode'] if home_map[:zip_code] != match_to_use['postalcode'] else change_map = nil end @@ -66,7 +77,7 @@ def perform(home_id) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metr change_map[:status] = Home.statuses[:canonicalized] home.update_columns change_map # rubocop:disable Rails/SkipsModelValidations else - # If find match, then move survey visits to existing home and delete + # If find match for existing_home, then move survey visits to existing home and delete ActiveRecord::Base.transaction do home.survey_visits.each do |sv| sv.home = existing_home diff --git a/backend/app/models/localized_survey_question.rb b/backend/app/models/localized_survey_question.rb new file mode 100644 index 00000000..484d6a3f --- /dev/null +++ b/backend/app/models/localized_survey_question.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class LocalizedSurveyQuestion < ApplicationRecord + belongs_to :survey_question +end diff --git a/backend/app/models/survey_question.rb b/backend/app/models/survey_question.rb index 3a9b6bea..15867f58 100644 --- a/backend/app/models/survey_question.rb +++ b/backend/app/models/survey_question.rb @@ -3,6 +3,24 @@ class SurveyQuestion < ApplicationRecord belongs_to :survey enum response_type: { radio: 0, text: 1 } + has_many :localized_survey_questions, dependent: nil + accepts_nested_attributes_for :localized_survey_questions validates :display_order, uniqueness: { scope: :survey_id } + + def text(language_code) + lsq = localized_survey_questions.find_by(language_code:) + + raise StandardError, "Survey question #{id} does not have localization '#{language_code}'" if lsq.nil? + + lsq.text + end + + def response_options(language_code) + lsq = localized_survey_questions.find_by(language_code:) + + raise StandardError, "Survey question #{id} does not have localization '#{language_code}'" if lsq.nil? + + lsq.response_options + end end diff --git a/backend/app/views/assignments/_assignment.json.jbuilder b/backend/app/views/assignments/_assignment.json.jbuilder index 5062dd2e..169c0bc3 100644 --- a/backend/app/views/assignments/_assignment.json.jbuilder +++ b/backend/app/views/assignments/_assignment.json.jbuilder @@ -10,9 +10,6 @@ end h[:visit_order] end -json.homes @sorted_homes do |home| - json.extract! home, :id, :street_number, :street_name, :unit_number, :city, :state, :zip_code, - :building_type, :visit_order, :latitude, :longitude - json.visited home.visited? - json.completed home.completed? +json.homes do + json.array! @sorted_homes, partial: 'homes/home', as: :home end diff --git a/backend/app/views/homes/_home.json.jbuilder b/backend/app/views/homes/_home.json.jbuilder index c84d40be..587525e0 100644 --- a/backend/app/views/homes/_home.json.jbuilder +++ b/backend/app/views/homes/_home.json.jbuilder @@ -4,4 +4,5 @@ json.extract! home, :id, :street_number, :street_name, :unit_number, :city, :sta :status, :user_added, :assignment_id, :visit_order, :latitude, :longitude json.visited home.visited? json.completed home.completed? +json.survey_visit_ids home.survey_visit_ids json.url home_url(home, format: :json) diff --git a/backend/app/views/surveys/_survey.json.jbuilder b/backend/app/views/surveys/_survey.json.jbuilder index 063c8705..8656b498 100644 --- a/backend/app/views/surveys/_survey.json.jbuilder +++ b/backend/app/views/surveys/_survey.json.jbuilder @@ -1,9 +1,11 @@ # frozen_string_literal: true json.extract! survey, :id, :title, :updated_at -json.survey_questions do - json.array!(survey.survey_questions.sort_by do |el| - el[:display_order] - end, :id, :display_order, :text, :response_type, :response_options) +json.survey_questions @survey.survey_questions.sort_by(&:display_order) do |sq| + json.id sq.id + json.display_order sq.display_order + json.response_type sq.response_type + json.question sq.text(@language_code) + json.response_options sq.response_options(@language_code) end json.url survey_url(survey, format: :json) diff --git a/backend/app/views/surveys/index.json.jbuilder b/backend/app/views/surveys/index.json.jbuilder index cdf31851..d7e40fbf 100644 --- a/backend/app/views/surveys/index.json.jbuilder +++ b/backend/app/views/surveys/index.json.jbuilder @@ -1,3 +1,8 @@ # frozen_string_literal: true -json.array! @surveys, partial: 'surveys/survey', as: :survey +json.array! @surveys do |s| + json.id s.id + json.title s.title + json.updated_at s.updated_at + json.url survey_url(s.id, format: :json) +end diff --git a/backend/db/migrate/20230920000437_create_localized_survey_questions.rb b/backend/db/migrate/20230920000437_create_localized_survey_questions.rb new file mode 100644 index 00000000..ade0b8f0 --- /dev/null +++ b/backend/db/migrate/20230920000437_create_localized_survey_questions.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class CreateLocalizedSurveyQuestions < ActiveRecord::Migration[7.0] + def change + create_table :localized_survey_questions do |t| + t.string :language_code + t.text :text + t.string :response_options, array: true + t.references :survey_question + + t.timestamps + end + + change_table :survey_questions, bulk: true do |t| + t.remove :text, type: :text + t.remove :response_options, type: :string, array: true + end + end +end diff --git a/backend/db/schema.rb b/backend/db/schema.rb index 08806692..d43e420f 100644 --- a/backend/db/schema.rb +++ b/backend/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_07_12_003916) do +ActiveRecord::Schema[7.0].define(version: 2023_09_20_000437) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -41,8 +41,6 @@ t.datetime "updated_at", null: false t.bigint "assignment_id" t.integer "visit_order" - t.integer "status", default: 0, null: false - t.boolean "user_added", default: false, null: false t.integer "bedroom_score" t.integer "floor_score" t.string "geometry" @@ -56,6 +54,8 @@ t.integer "total_score_x" t.string "yr_built_category" t.integer "yr_built_score" + t.integer "status", default: 0, null: false + t.boolean "user_added", default: false, null: false t.index ["assignment_id", "visit_order"], name: "index_homes_on_assignment_id_and_visit_order", unique: true t.index ["assignment_id"], name: "index_homes_on_assignment_id" end @@ -68,6 +68,16 @@ t.index ["jti"], name: "index_jwt_denylists_on_jti" end + create_table "localized_survey_questions", force: :cascade do |t| + t.string "language_code" + t.text "text" + t.string "response_options", array: true + t.bigint "survey_question_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["survey_question_id"], name: "index_localized_survey_questions_on_survey_question_id" + end + create_table "survey_answers", force: :cascade do |t| t.text "answer" t.bigint "survey_response_id", null: false @@ -79,14 +89,11 @@ end create_table "survey_questions", force: :cascade do |t| - t.text "text" t.integer "response_type" - t.string "response_options", array: true t.bigint "survey_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "display_order", default: 0, null: false - t.index ["response_options"], name: "index_survey_questions_on_response_options", using: :gin t.index ["survey_id", "display_order"], name: "index_survey_questions_on_survey_id_and_display_order", unique: true t.index ["survey_id"], name: "index_survey_questions_on_survey_id" end diff --git a/backend/db/seed_importer.rb b/backend/db/seed_importer.rb index 747cc890..520f67cb 100644 --- a/backend/db/seed_importer.rb +++ b/backend/db/seed_importer.rb @@ -25,7 +25,18 @@ def import_seed_data(path, clustered_ordered_parcels) chunk.each do |data_hash| data_hash[:response_options] = (data_hash[:response_options].nil? ? [] : data_hash[:response_options].split('/', -1)) - question = SurveyQuestion.new(data_hash) + fixed_data_hash = { + display_order: data_hash[:display_order], + response_type: data_hash[:response_type], + localized_survey_questions: [ + LocalizedSurveyQuestion.new({ + text: data_hash[:text], + response_options: data_hash[:response_options], + language_code: 'en' + }) + ] + } + question = SurveyQuestion.new(fixed_data_hash) question.survey = survey question.save! end diff --git a/backend/spec/jobs/canonicalize_address_job_spec.rb b/backend/spec/jobs/canonicalize_address_job_spec.rb index 7d81ac46..5c3f0108 100644 --- a/backend/spec/jobs/canonicalize_address_job_spec.rb +++ b/backend/spec/jobs/canonicalize_address_job_spec.rb @@ -3,5 +3,10 @@ require 'rails_helper' RSpec.describe CanonicalizeAddressJob, type: :job do - pending "add some examples to (or delete) #{__FILE__}" + let(:home) { create(:home) } + it 'canonicalizes an address properly' do + CanonicalizeAddressJob.perform_now(home.id) + home.reload + expect(home.street_number).not_to be_nil + end end