diff --git a/.github/actions/build-and-push/action.yml b/.github/actions/build-and-push/action.yml new file mode 100644 index 00000000000..7739c7304d5 --- /dev/null +++ b/.github/actions/build-and-push/action.yml @@ -0,0 +1,25 @@ +name: Build and Push +description: Build and push Docker image to the registry +inputs: + acr_registry: + description: Azure Container Registry to push the image to + required: true + acr_username: + description: Azure Container Registry username + required: true + acr_password: + description: Azure Container Registry password + required: true + +runs: + using: composite + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to ACR + shell: bash + run: docker login ${{ inputs.acr_registry }} -u ${{ inputs.acr_username }} -p ${{ inputs.acr_password }} + - name: Build and push Docker images + working-directory: ./backend + shell: bash + run: ./build_and_push.sh diff --git a/.github/actions/build-frontend/action.yml b/.github/actions/build-frontend/action.yml index 4ec7857fceb..33f2088fd19 100644 --- a/.github/actions/build-frontend/action.yml +++ b/.github/actions/build-frontend/action.yml @@ -1,33 +1,48 @@ name: Build SimpleReport Front End description: Build the React application inputs: - deploy-env: + deploy_env: description: The environment being deployed (e.g. "prod" or "test") required: true - smarty-streets-key: + smarty_streets_key: description: The Smarty-Streets API token for this environment. (Should be fetched from vault but is not) required: true - base-domain-name: + base_domain_name: description: The domain where the application is deployed (e.g. "simplereport.gov" or "test.simplereport.gov") required: false - client-tarball: + client_tarball: description: The path to the tar file containing the client code to deploy required: true - is-training-site: + is_training_site: description: If this is set, special training branding will be applied. required: false - okta-enabled: + okta_enabled: description: If this is set, the app will redirect to Okta if no one is logged in. required: true - okta-url: + okta_url: description: The Okta instance to redirect to. required: false - okta-client-id: + okta_client_id: description: The Okta client ID for this environment. required: false + azure_creds: + description: The Azure credentials for this environment. + required: true runs: using: composite steps: + - uses: actions/setup-node@v3.8.1 + with: + node-version: ${{ env.NODE_VERSION }} + - name: Use cache for node_modules + uses: actions/cache@v3.3.2 + with: + path: | + ./frontend/node_modules + key: npm-${{ env.NODE_VERSION }}-${{ hashFiles('frontend/yarn.lock', 'frontend/package.json') }} + - uses: azure/login@v1 + with: + creds: ${{ inputs.azure_creds }} - name: Install dependencies working-directory: ./frontend shell: bash @@ -40,34 +55,34 @@ runs: working-directory: ./frontend run: | echo "::group::Set build variables" - ENVLVL=${{inputs.deploy-env}} + ENVLVL=${{ inputs.deploy_env }} ENVLVL=${ENVLVL//[[:digit:]]/} echo "Environment level: $ENVLVL" az config set extension.use_dynamic_install=yes_without_prompt INSIGHTS_CONNECTION_STRING=$( az monitor app-insights component show \ -g prime-simple-report-$ENVLVL \ - -a prime-simple-report-${{inputs.deploy-env}}-insights \ + -a prime-simple-report-${{ inputs.deploy_env }}-insights \ | jq -r '.connectionString') echo "REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING=${INSIGHTS_CONNECTION_STRING}" > .env.production.local - if [[ -n "${{ inputs.base-domain-name }}" ]] - then echo "REACT_APP_BASE_URL=https://${{inputs.base-domain-name}}" >> .env.production.local + if [[ -n "${{ inputs.base_domain_name }}" ]] + then echo "REACT_APP_BASE_URL=https://${{ inputs.base_domain_name }}" >> .env.production.local fi - if [[ "true" == "${{ inputs.is-training-site }}" ]] + if [[ "true" == "${{ inputs.is_training_site }}" ]] then echo "REACT_APP_IS_TRAINING_SITE=true" >> .env.production.local fi - if [[ "true" == "${{ inputs.okta-enabled }}" ]]; then + if [[ "true" == "${{ inputs.okta_enabled }}" ]]; then echo "REACT_APP_OKTA_ENABLED=true" >> .env.production.local - echo "REACT_APP_OKTA_URL=${{inputs.okta-url}}" >> .env.production.local - echo "REACT_APP_OKTA_CLIENT_ID=${{inputs.okta-client-id}}" >> .env.production.local + echo "REACT_APP_OKTA_URL=${{ inputs.okta_url }}" >> .env.production.local + echo "REACT_APP_OKTA_CLIENT_ID=${{ inputs.okta_client_id }}" >> .env.production.local fi echo "::endgroup::" - name: Build deployable application shell: bash working-directory: ./frontend env: - REACT_APP_SMARTY_STREETS_KEY: ${{ inputs.smarty-streets-key }} - DEPLOY_ENV: ${{ inputs.deploy-env }} + REACT_APP_SMARTY_STREETS_KEY: ${{ inputs.smarty_streets_key }} + DEPLOY_ENV: ${{ inputs.deploy_env }} run: | echo "::group::Build application" yarn run build @@ -76,5 +91,12 @@ runs: shell: bash run: | echo "::group::Create application archive" - tar -C ./frontend/build -czf ${{inputs.client-tarball}} . + tar -C ./frontend/build -czf ${{ inputs.client_tarball }} . echo "::endgroup::" + - name: Save compiled frontend application + uses: actions/upload-artifact@v3 + if: success() + with: + name: frontend-tarball + path: client.tgz + retention-days: 1 diff --git a/.github/actions/deploy-application/action.yml b/.github/actions/deploy-application/action.yml index 4105686a2d0..db5249b596b 100644 --- a/.github/actions/deploy-application/action.yml +++ b/.github/actions/deploy-application/action.yml @@ -1,28 +1,39 @@ name: Deploy SimpleReport Application description: Promote API from secondary slot, and deploy client from tarball inputs: - deploy-env: + deploy_env: description: The environment being deployed (e.g. "prod" or "test") required: true - client-tarball: + client_tarball: description: The path to the tar file containing the client code to deploy required: true + azure_creds: + description: The Azure credentials for this environment. + required: true + runs: using: composite steps: + - uses: azure/login@v1 + with: + creds: ${{ inputs.azure_creds }} + - name: Retrieve frontend build + uses: actions/download-artifact@v3 + with: + name: frontend-tarball - name: Unpack client shell: bash run: | echo "::group::Unpack client" mkdir client-build; - tar -C client-build -zxvf ${{inputs.client-tarball}} + tar -C client-build -zxvf ${{ inputs.client_tarball }} echo "::endgroup::" - name: Promote API to production and verify that it is ready shell: bash working-directory: ./ops run: | echo "::group::Promote API and verify readiness" - make promote-${{ env.DEPLOY_ENV }} check-${{ env.DEPLOY_ENV }}-readiness + make promote-${{ inputs.deploy_env }} check-${{ inputs.deploy_env }}-readiness echo "::endgroup::" - name: Check for production app readiness shell: bash @@ -33,7 +44,7 @@ runs: run: | echo "::group::Deploy frontend app" az storage blob upload-batch -s client-build/ -d '$web' \ - --account-name simplereport${{ inputs.deploy-env }}app \ + --account-name simplereport${{ inputs.deploy_env }}app \ --destination-path '/app' \ --overwrite echo "::endgroup::" diff --git a/.github/actions/build-docker-image/action.yml b/.github/actions/docker-buildx/action.yml similarity index 84% rename from .github/actions/build-docker-image/action.yml rename to .github/actions/docker-buildx/action.yml index a7b56703da6..054927b8e7d 100644 --- a/.github/actions/build-docker-image/action.yml +++ b/.github/actions/docker-buildx/action.yml @@ -1,27 +1,20 @@ name: Build Docker Image description: Build and push Docker image to the registry inputs: - acr_registry: - description: Azure Container Registry to push the image to - required: true - acr_username: - description: Azure Container Registry username - required: true - acr_password: - description: Azure Container Registry password - required: true build_args: description: Build arguments to pass to the Docker build required: false context: description: Path to the build context - required: true + required: false + default: ./ file: description: Path to the Dockerfile required: true gh_registry: description: Registry to push the image to - required: true + required: false + default: ghcr.io gh_username: description: Github username required: true @@ -33,10 +26,8 @@ inputs: required: true platform: description: Platform to build the image for - required: true - version_tag: - description: Version tag to use for the image required: false + default: linux/amd64 outputs: version: description: Version of the image that was built diff --git a/.github/actions/stg-wait-for-slot-commit/action.yml b/.github/actions/stg-wait-for-slot-commit/action.yml new file mode 100644 index 00000000000..5c6a1b92976 --- /dev/null +++ b/.github/actions/stg-wait-for-slot-commit/action.yml @@ -0,0 +1,14 @@ +name: Terraform Action wait for slot commit +description: Build and push Docker image to the registry +inputs: + deploy_env: + description: The environment to deploy to + required: true + +runs: + using: composite + steps: + - name: Wait for correct commit to be deployed in staging slot + working-directory: ./ops + shell: bash + run: make wait-for-${{ inputs.deploy_env }}-slot-commit diff --git a/.github/actions/stg-wait-for-slot-readiness/action.yml b/.github/actions/stg-wait-for-slot-readiness/action.yml new file mode 100644 index 00000000000..22cfbd4a542 --- /dev/null +++ b/.github/actions/stg-wait-for-slot-readiness/action.yml @@ -0,0 +1,14 @@ +name: Terraform Action wait for slot readiness +description: Build and push Docker image to the registry +inputs: + deploy_env: + description: The environment to deploy to + required: true + +runs: + using: composite + steps: + - name: Wait for staging deploy to be ready + working-directory: ./ops + shell: bash + run: make wait-for-${{ inputs.deploy_env }}-slot-readiness diff --git a/.github/actions/tf-deploy/action.yml b/.github/actions/tf-deploy/action.yml new file mode 100644 index 00000000000..a3a8555be12 --- /dev/null +++ b/.github/actions/tf-deploy/action.yml @@ -0,0 +1,58 @@ +name: Terraform Action Deployment +description: Build and push Docker image to the registry +inputs: + azure_creds: + description: Azure credentials + required: true + deploy_env: + description: The environment to deploy to + required: true + terraform_arm_client_id: + description: Terraform ARM client ID + required: true + terraform_arm_client_secret: + description: Terraform ARM client secret + required: true + terraform_arm_subscription_id: + description: Terraform ARM subscription ID + required: true + terraform_arm_tenant_id: + description: Terraform ARM tenant ID + required: true + okta_api_token: + description: Okta API token + required: true + +runs: + using: composite + steps: + - uses: azure/login@v1 + with: + creds: ${{ inputs.azure_creds }} + - uses: hashicorp/setup-terraform@v2.0.3 + with: + terraform_version: 1.3.3 + - name: Build ReportStream function app + uses: ./.github/actions/build-reportstream-functions + with: + deploy-env: ${{ inputs.deploy_env }} + - name: Terraform Init + working-directory: ./ops + env: # all Azure interaction is through Terraform + ARM_CLIENT_ID: ${{ inputs.terraform_arm_client_id }} + ARM_CLIENT_SECRET: ${{ inputs.terraform_arm_client_secret }} + ARM_SUBSCRIPTION_ID: ${{ inputs.terraform_arm_subscription_id }} + ARM_TENANT_ID: ${{ inputs.terraform_arm_tenant_id }} + OKTA_API_TOKEN: ${{ inputs.okta_api_token }} + shell: bash + run: make init-${{ inputs.deploy_env }} + - name: Terraform deploy (infrastructure and staging slot) + working-directory: ./ops + env: # all Azure interaction is through Terraform + ARM_CLIENT_ID: ${{ inputs.terraform_arm_client_id }} + ARM_CLIENT_SECRET: ${{ inputs.terraform_arm_client_secret }} + ARM_SUBSCRIPTION_ID: ${{ inputs.terraform_arm_subscription_id }} + ARM_TENANT_ID: ${{ inputs.terraform_arm_tenant_id }} + OKTA_API_TOKEN: ${{ inputs.okta_api_token }} + shell: bash + run: make deploy-${{ inputs.deploy_env }} diff --git a/.github/workflows/deployDemo.yml b/.github/workflows/deployDemo.yml index 0ffdc7e7143..053cf304e9c 100644 --- a/.github/workflows/deployDemo.yml +++ b/.github/workflows/deployDemo.yml @@ -9,108 +9,77 @@ on: env: DEPLOY_ENV: demo NODE_VERSION: 18 + concurrency: group: demo-deploy cancel-in-progress: false jobs: build_docker: - if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest - defaults: - run: - working-directory: ./backend steps: - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to ACR - run: docker login ${{ secrets.ACR_REPO_URL }} -u ${{ secrets.ACR_ADMIN_USERNAME }} -p ${{ secrets.ACR_ADMIN_PASWORD }} - - name: Build and push Docker images - run: ./build_and_push.sh - prerelease_backend: - if: ${{ github.event.workflow_run.conclusion == 'success' }} + - name: Build and Push backend + uses: ./.github/actions/build-and-push + with: + acr_registry: ${{ secrets.ACR_REPO_URL }} + acr_username: ${{ secrets.ACR_ADMIN_USERNAME }} + acr_password: ${{ secrets.ACR_ADMIN_PASWORD }} + + build_frontend: runs-on: ubuntu-latest - needs: [build_frontend, build_docker] - defaults: - run: - working-directory: ./ops - env: # all Azure interaction is through terraform - ARM_CLIENT_ID: ${{ secrets.TERRAFORM_ARM_CLIENT_ID }} - ARM_CLIENT_SECRET: ${{ secrets.TERRAFORM_ARM_CLIENT_SECRET }} - ARM_SUBSCRIPTION_ID: ${{ secrets.TERRAFORM_ARM_SUBSCRIPTION_ID }} - ARM_TENANT_ID: ${{ secrets.TERRAFORM_ARM_TENANT_ID }} - OKTA_API_TOKEN: ${{ secrets.OKTA_API_TOKEN }} steps: - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: hashicorp/setup-terraform@v2.0.3 + - uses: ./.github/actions/build-frontend + name: Build front-end application with: - terraform_version: 1.3.3 - - name: Terraform Init - run: make init-${{ env.DEPLOY_ENV }} - - name: Terraform deploy (infrastructure and staging slot) - run: make deploy-${{ env.DEPLOY_ENV }} - - name: Wait for correct commit to be deployed in staging slot - timeout-minutes: 5 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-commit - - name: Wait for staging deploy to be ready - timeout-minutes: 1 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-readiness - build_frontend: - if: ${{ github.event.workflow_run.conclusion == 'success' }} + azure_creds: ${{ secrets.AZURE_CREDENTIALS }} + client_tarball: ./client.tgz + deploy_env: ${{env.DEPLOY_ENV}} + smarty_streets_key: ${{ secrets.SMARTY_STREETS_KEY }} + okta_enabled: false + + prerelease_backend: runs-on: ubuntu-latest + needs: [build_frontend, build_docker] steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3.8.1 - with: - node-version: ${{env.NODE_VERSION}} - - name: Use cache for node_modules - uses: actions/cache@v3.3.2 + - uses: ./.github/actions/tf-deploy + name: Deploy with Terraform with: - path: | - ./frontend/node_modules - key: npm-${{env.NODE_VERSION}}-${{ hashFiles('frontend/yarn.lock', 'frontend/package.json') }} - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: ./.github/actions/build-frontend - name: Build front-end application + azure_creds: ${{ secrets.AZURE_CREDENTIALS }} + deploy_env: ${{ env.DEPLOY_ENV }} + terraform_arm_client_id: ${{ secrets.TERRAFORM_ARM_CLIENT_ID }} + terraform_arm_client_secret: ${{ secrets.TERRAFORM_ARM_CLIENT_SECRET }} + terraform_arm_subscription_id: ${{ secrets.TERRAFORM_ARM_SUBSCRIPTION_ID }} + terraform_arm_tenant_id: ${{ secrets.TERRAFORM_ARM_TENANT_ID }} + okta_api_token: ${{ secrets.OKTA_API_TOKEN }} + - uses: ./.github/actions/stg-wait-for-slot-commit + name: Wait for correct commit to be deployed in staging slot + timeout-minutes: 5 with: - deploy-env: ${{env.DEPLOY_ENV}} - smarty-streets-key: ${{ secrets.SMARTY_STREETS_KEY }} - client-tarball: ./client.tgz - okta-enabled: false - - name: Save compiled frontend application - uses: actions/upload-artifact@v3 - if: success() + deploy_env: ${{ env.DEPLOY_ENV }} + - uses: ./.github/actions/stg-wait-for-slot-readiness + name: Wait for staging deploy to be ready + timeout-minutes: 1 with: - name: frontend-tarball - path: client.tgz - retention-days: 1 + deploy_env: ${{ env.DEPLOY_ENV }} + deploy: - if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest environment: - name: Demo - url: https://demo.simplereport.gov + name: ${{ env.DEPLOY_ENV }} + url: https://${{ env.DEPLOY_ENV }}.simplereport.gov needs: [prerelease_backend] steps: - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - name: Retrieve frontend build - uses: actions/download-artifact@v3 - with: - name: frontend-tarball - name: Promote and deploy uses: ./.github/actions/deploy-application with: - client-tarball: client.tgz - deploy-env: ${{env.DEPLOY_ENV}} + azure_creds: ${{ secrets.AZURE_CREDENTIALS }} + client_tarball: client.tgz + deploy_env: ${{ env.DEPLOY_ENV }} + slack_alert: runs-on: ubuntu-latest if: failure() diff --git a/.github/workflows/deployDev.yml b/.github/workflows/deployDev.yml index 776707d9ac3..6e7ead82146 100644 --- a/.github/workflows/deployDev.yml +++ b/.github/workflows/deployDev.yml @@ -1,120 +1,96 @@ name: Deploy Dev +run-name: Deploy to ${{ inputs.deploy_env }} by @${{ github.actor }} on: workflow_dispatch: + inputs: + deploy_env: + description: 'The environment to deploy to' + required: true + type: choice + options: + - "" + - dev + - dev2 + - dev3 + - dev4 + - dev5 + - dev6 + - dev7 + - pentest env: - DEPLOY_ENV: dev NODE_VERSION: 18 concurrency: - group: dev-deploy + group: ${{ github.event.inputs.deploy_env }}-deploy cancel-in-progress: false jobs: build_docker: runs-on: ubuntu-latest - defaults: - run: - working-directory: ./backend steps: - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to ACR - run: docker login ${{ secrets.ACR_REPO_URL }} -u ${{ secrets.ACR_ADMIN_USERNAME }} -p ${{ secrets.ACR_ADMIN_PASWORD }} - - name: Build and push Docker images - run: ./build_and_push.sh - prerelease_backend: + - name: Build and Push backend + uses: ./.github/actions/build-and-push + with: + acr_registry: ${{ secrets.ACR_REPO_URL }} + acr_username: ${{ secrets.ACR_ADMIN_USERNAME }} + acr_password: ${{ secrets.ACR_ADMIN_PASWORD }} + + build_frontend: runs-on: ubuntu-latest - needs: [build_frontend, build_docker] - defaults: - run: - working-directory: ./ops - env: # all Azure interaction is through Terraform - ARM_CLIENT_ID: ${{ secrets.TERRAFORM_ARM_CLIENT_ID }} - ARM_CLIENT_SECRET: ${{ secrets.TERRAFORM_ARM_CLIENT_SECRET }} - ARM_SUBSCRIPTION_ID: ${{ secrets.TERRAFORM_ARM_SUBSCRIPTION_ID }} - ARM_TENANT_ID: ${{ secrets.TERRAFORM_ARM_TENANT_ID }} - OKTA_API_TOKEN: ${{ secrets.OKTA_API_TOKEN_NONPROD }} + environment: ${{ inputs.deploy_env }} steps: - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: hashicorp/setup-terraform@v2.0.3 - with: - terraform_version: 1.3.3 - - name: Build ReportStream function app - uses: ./.github/actions/build-reportstream-functions + - uses: ./.github/actions/build-frontend + name: Build front-end application with: - deploy-env: ${{env.DEPLOY_ENV}} - - name: Terraform Init - run: make init-${{ env.DEPLOY_ENV }} - - name: Terraform deploy (infrastructure and staging slot) - run: make deploy-${{ env.DEPLOY_ENV }} - - name: Wait for correct commit to be deployed in staging slot - timeout-minutes: 5 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-commit - - name: Wait for staging deploy to be ready - timeout-minutes: 1 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-readiness - build_frontend: + azure_creds: ${{ secrets.AZURE_CREDENTIALS }} + client_tarball: ./client.tgz + deploy_env: ${{ inputs.deploy_env }} + smarty_streets_key: ${{ secrets.SMARTY_STREETS_KEY }} + okta_enabled: true + okta_url: https://hhs-prime.oktapreview.com + okta_client_id: ${{ vars.OKTA_CLIENT_ID }} + + prerelease_backend: runs-on: ubuntu-latest + needs: [build_frontend, build_docker] steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3.8.1 - with: - node-version: ${{env.NODE_VERSION}} - - name: Use cache for node_modules - uses: actions/cache@v3.3.2 + - uses: ./.github/actions/tf-deploy + name: Deploy with Terraform with: - path: | - ./frontend/node_modules - key: npm-${{env.NODE_VERSION}}-${{ hashFiles('frontend/yarn.lock', 'frontend/package.json') }} - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: ./.github/actions/build-frontend - name: Build front-end application + azure_creds: ${{ secrets.AZURE_CREDENTIALS }} + deploy_env: ${{ inputs.deploy_env }} + terraform_arm_client_id: ${{ secrets.TERRAFORM_ARM_CLIENT_ID }} + terraform_arm_client_secret: ${{ secrets.TERRAFORM_ARM_CLIENT_SECRET }} + terraform_arm_subscription_id: ${{ secrets.TERRAFORM_ARM_SUBSCRIPTION_ID }} + terraform_arm_tenant_id: ${{ secrets.TERRAFORM_ARM_TENANT_ID }} + okta_api_token: ${{ secrets.OKTA_API_TOKEN_NONPROD }} + - uses: ./.github/actions/stg-wait-for-slot-commit + name: Wait for correct commit to be deployed in staging slot + timeout-minutes: 5 with: - deploy-env: ${{env.DEPLOY_ENV}} - smarty-streets-key: ${{ secrets.SMARTY_STREETS_KEY }} - client-tarball: ./client.tgz - okta-enabled: true - okta-url: https://hhs-prime.oktapreview.com - okta-client-id: 0oa1khbp5n2wTfe281d7 - - name: Save compiled frontend application - uses: actions/upload-artifact@v3 - if: success() + deploy_env: ${{ inputs.deploy_env }} + - uses: ./.github/actions/stg-wait-for-slot-readiness + name: Wait for staging deploy to be ready + timeout-minutes: 1 with: - name: frontend-tarball - path: client.tgz - retention-days: 1 + deploy_env: ${{ inputs.deploy_env }} + deploy: runs-on: ubuntu-latest environment: - name: Dev - url: https://dev.simplereport.gov + name: ${{ inputs.deploy_env }} + url: https://${{ inputs.deploy_env }}.simplereport.gov needs: [prerelease_backend] steps: - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - name: Retrieve frontend build - uses: actions/download-artifact@v3 - with: - name: frontend-tarball - name: Promote and deploy uses: ./.github/actions/deploy-application with: - client-tarball: client.tgz - deploy-env: ${{ env.DEPLOY_ENV }} - - name: Sendgrid maintenance banner deploy - uses: ./.github/actions/maintenance-banner-deploy - with: - active: "true" - header: "Dev Notice:" - message: "This environment has email delivery enabled. Do not use real email addresses." - env: ${{ env.DEPLOY_ENV }} + azure_creds: ${{ secrets.AZURE_CREDENTIALS }} + client_tarball: client.tgz + deploy_env: ${{ inputs.deploy_env }} diff --git a/.github/workflows/deployDev2.yml b/.github/workflows/deployDev2.yml deleted file mode 100644 index 0d5c4d1b14f..00000000000 --- a/.github/workflows/deployDev2.yml +++ /dev/null @@ -1,112 +0,0 @@ -name: Deploy Dev2 - -on: - workflow_dispatch: - -env: - DEPLOY_ENV: dev2 - NODE_VERSION: 18 -concurrency: - group: dev2-deploy - cancel-in-progress: false - -jobs: - build_docker: - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./backend - steps: - - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to ACR - run: docker login ${{ secrets.ACR_REPO_URL }} -u ${{ secrets.ACR_ADMIN_USERNAME }} -p ${{ secrets.ACR_ADMIN_PASWORD }} - - name: Build and push Docker images - run: ./build_and_push.sh - prerelease_backend: - runs-on: ubuntu-latest - needs: [build_frontend, build_docker] - defaults: - run: - working-directory: ./ops - env: # all Azure interaction is through Terraform - ARM_CLIENT_ID: ${{ secrets.TERRAFORM_ARM_CLIENT_ID }} - ARM_CLIENT_SECRET: ${{ secrets.TERRAFORM_ARM_CLIENT_SECRET }} - ARM_SUBSCRIPTION_ID: ${{ secrets.TERRAFORM_ARM_SUBSCRIPTION_ID }} - ARM_TENANT_ID: ${{ secrets.TERRAFORM_ARM_TENANT_ID }} - OKTA_API_TOKEN: ${{ secrets.OKTA_API_TOKEN_NONPROD }} - steps: - - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: hashicorp/setup-terraform@v2.0.3 - with: - terraform_version: 1.3.3 - - name: Build ReportStream function app - uses: ./.github/actions/build-reportstream-functions - with: - deploy-env: ${{env.DEPLOY_ENV}} - - name: Terraform Init - run: make init-${{ env.DEPLOY_ENV }} - - name: Terraform deploy (infrastructure and staging slot) - run: make deploy-${{ env.DEPLOY_ENV }} - - name: Wait for correct commit to be deployed in staging slot - timeout-minutes: 5 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-commit - - name: Wait for staging deploy to be ready - timeout-minutes: 1 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-readiness - build_frontend: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v3.8.1 - with: - node-version: ${{env.NODE_VERSION}} - - name: Use cache for node_modules - uses: actions/cache@v3.3.2 - with: - path: | - ./frontend/node_modules - key: npm-${{env.NODE_VERSION}}-${{ hashFiles('frontend/yarn.lock', 'frontend/package.json') }} - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: ./.github/actions/build-frontend - name: Build front-end application - with: - deploy-env: ${{env.DEPLOY_ENV}} - smarty-streets-key: ${{ secrets.SMARTY_STREETS_KEY }} - client-tarball: ./client.tgz - okta-enabled: true - okta-url: https://hhs-prime.oktapreview.com - okta-client-id: 0oa3ii5dwmasAsCww1d7 - - name: Save compiled frontend application - uses: actions/upload-artifact@v3 - if: success() - with: - name: frontend-tarball - path: client.tgz - retention-days: 1 - deploy: - runs-on: ubuntu-latest - environment: - name: Dev2 - url: https://${{env.DEPLOY_ENV}}.simplereport.gov - needs: [prerelease_backend] - steps: - - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - name: Retrieve frontend build - uses: actions/download-artifact@v3 - with: - name: frontend-tarball - - name: Promote and deploy - uses: ./.github/actions/deploy-application - with: - client-tarball: client.tgz - deploy-env: ${{env.DEPLOY_ENV}} diff --git a/.github/workflows/deployDev3.yml b/.github/workflows/deployDev3.yml deleted file mode 100644 index 693308ca77c..00000000000 --- a/.github/workflows/deployDev3.yml +++ /dev/null @@ -1,112 +0,0 @@ -name: Deploy Dev3 - -on: - workflow_dispatch: - -env: - DEPLOY_ENV: dev3 - NODE_VERSION: 18 -concurrency: - group: dev3-deploy - cancel-in-progress: false - -jobs: - build_docker: - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./backend - steps: - - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to ACR - run: docker login ${{ secrets.ACR_REPO_URL }} -u ${{ secrets.ACR_ADMIN_USERNAME }} -p ${{ secrets.ACR_ADMIN_PASWORD }} - - name: Build and push Docker images - run: ./build_and_push.sh - prerelease_backend: - runs-on: ubuntu-latest - needs: [build_frontend, build_docker] - defaults: - run: - working-directory: ./ops - env: # all Azure interaction is through Terraform - ARM_CLIENT_ID: ${{ secrets.TERRAFORM_ARM_CLIENT_ID }} - ARM_CLIENT_SECRET: ${{ secrets.TERRAFORM_ARM_CLIENT_SECRET }} - ARM_SUBSCRIPTION_ID: ${{ secrets.TERRAFORM_ARM_SUBSCRIPTION_ID }} - ARM_TENANT_ID: ${{ secrets.TERRAFORM_ARM_TENANT_ID }} - OKTA_API_TOKEN: ${{ secrets.OKTA_API_TOKEN_NONPROD }} - steps: - - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: hashicorp/setup-terraform@v2.0.3 - with: - terraform_version: 1.3.3 - - name: Build ReportStream function app - uses: ./.github/actions/build-reportstream-functions - with: - deploy-env: ${{env.DEPLOY_ENV}} - - name: Terraform Init - run: make init-${{ env.DEPLOY_ENV }} - - name: Terraform deploy (infrastructure and staging slot) - run: make deploy-${{ env.DEPLOY_ENV }} - - name: Wait for correct commit to be deployed in staging slot - timeout-minutes: 5 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-commit - - name: Wait for staging deploy to be ready - timeout-minutes: 1 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-readiness - build_frontend: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v3.8.1 - with: - node-version: ${{env.NODE_VERSION}} - - name: Use cache for node_modules - uses: actions/cache@v3.3.2 - with: - path: | - ./frontend/node_modules - key: npm-${{env.NODE_VERSION}}-${{ hashFiles('frontend/yarn.lock', 'frontend/package.json') }} - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: ./.github/actions/build-frontend - name: Build front-end application - with: - deploy-env: ${{env.DEPLOY_ENV}} - smarty-streets-key: ${{ secrets.SMARTY_STREETS_KEY }} - client-tarball: ./client.tgz - okta-enabled: true - okta-url: https://hhs-prime.oktapreview.com - okta-client-id: 0oa3ivrvd9Jhvt8Sb1d7 - - name: Save compiled frontend application - uses: actions/upload-artifact@v3 - if: success() - with: - name: frontend-tarball - path: client.tgz - retention-days: 1 - deploy: - runs-on: ubuntu-latest - environment: - name: Dev3 - url: https://${{env.DEPLOY_ENV}}.simplereport.gov - needs: [prerelease_backend] - steps: - - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - name: Retrieve frontend build - uses: actions/download-artifact@v3 - with: - name: frontend-tarball - - name: Promote and deploy - uses: ./.github/actions/deploy-application - with: - client-tarball: client.tgz - deploy-env: ${{env.DEPLOY_ENV}} diff --git a/.github/workflows/deployDev4.yml b/.github/workflows/deployDev4.yml deleted file mode 100644 index cd9273b2e58..00000000000 --- a/.github/workflows/deployDev4.yml +++ /dev/null @@ -1,112 +0,0 @@ -name: Deploy Dev4 - -on: - workflow_dispatch: - -env: - DEPLOY_ENV: dev4 - NODE_VERSION: 18 -concurrency: - group: dev4-deploy - cancel-in-progress: false - -jobs: - build_docker: - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./backend - steps: - - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to ACR - run: docker login ${{ secrets.ACR_REPO_URL }} -u ${{ secrets.ACR_ADMIN_USERNAME }} -p ${{ secrets.ACR_ADMIN_PASWORD }} - - name: Build and push Docker images - run: ./build_and_push.sh - prerelease_backend: - runs-on: ubuntu-latest - needs: [build_frontend, build_docker] - defaults: - run: - working-directory: ./ops - env: # all Azure interaction is through Terraform - ARM_CLIENT_ID: ${{ secrets.TERRAFORM_ARM_CLIENT_ID }} - ARM_CLIENT_SECRET: ${{ secrets.TERRAFORM_ARM_CLIENT_SECRET }} - ARM_SUBSCRIPTION_ID: ${{ secrets.TERRAFORM_ARM_SUBSCRIPTION_ID }} - ARM_TENANT_ID: ${{ secrets.TERRAFORM_ARM_TENANT_ID }} - OKTA_API_TOKEN: ${{ secrets.OKTA_API_TOKEN_NONPROD }} - steps: - - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: hashicorp/setup-terraform@v2.0.3 - with: - terraform_version: 1.3.3 - - name: Build ReportStream function app - uses: ./.github/actions/build-reportstream-functions - with: - deploy-env: ${{env.DEPLOY_ENV}} - - name: Terraform Init - run: make init-${{ env.DEPLOY_ENV }} - - name: Terraform deploy (infrastructure and staging slot) - run: make deploy-${{ env.DEPLOY_ENV }} - - name: Wait for correct commit to be deployed in staging slot - timeout-minutes: 5 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-commit - - name: Wait for staging deploy to be ready - timeout-minutes: 1 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-readiness - build_frontend: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v3.8.1 - with: - node-version: ${{env.NODE_VERSION}} - - name: Use cache for node_modules - uses: actions/cache@v3.3.2 - with: - path: | - ./frontend/node_modules - key: npm-${{env.NODE_VERSION}}-${{ hashFiles('frontend/yarn.lock', 'frontend/package.json') }} - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: ./.github/actions/build-frontend - name: Build front-end application - with: - deploy-env: ${{env.DEPLOY_ENV}} - smarty-streets-key: ${{ secrets.SMARTY_STREETS_KEY }} - client-tarball: ./client.tgz - okta-enabled: true - okta-url: https://hhs-prime.oktapreview.com - okta-client-id: 0oa3j1kbqp4ip5Osz1d7 - - name: Save compiled frontend application - uses: actions/upload-artifact@v3 - if: success() - with: - name: frontend-tarball - path: client.tgz - retention-days: 1 - deploy: - runs-on: ubuntu-latest - environment: - name: Dev4 - url: https://${{env.DEPLOY_ENV}}.simplereport.gov - needs: [prerelease_backend] - steps: - - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - name: Retrieve frontend build - uses: actions/download-artifact@v3 - with: - name: frontend-tarball - - name: Promote and deploy - uses: ./.github/actions/deploy-application - with: - client-tarball: client.tgz - deploy-env: ${{env.DEPLOY_ENV}} diff --git a/.github/workflows/deployDev5.yml b/.github/workflows/deployDev5.yml deleted file mode 100644 index 94f0faed0c7..00000000000 --- a/.github/workflows/deployDev5.yml +++ /dev/null @@ -1,112 +0,0 @@ -name: Deploy Dev5 - -on: - workflow_dispatch: - -env: - DEPLOY_ENV: dev5 - NODE_VERSION: 18 -concurrency: - group: dev5-deploy - cancel-in-progress: false - -jobs: - build_docker: - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./backend - steps: - - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to ACR - run: docker login ${{ secrets.ACR_REPO_URL }} -u ${{ secrets.ACR_ADMIN_USERNAME }} -p ${{ secrets.ACR_ADMIN_PASWORD }} - - name: Build and push Docker images - run: ./build_and_push.sh - prerelease_backend: - runs-on: ubuntu-latest - needs: [build_frontend, build_docker] - defaults: - run: - working-directory: ./ops - env: # all Azure interaction is through Terraform - ARM_CLIENT_ID: ${{ secrets.TERRAFORM_ARM_CLIENT_ID }} - ARM_CLIENT_SECRET: ${{ secrets.TERRAFORM_ARM_CLIENT_SECRET }} - ARM_SUBSCRIPTION_ID: ${{ secrets.TERRAFORM_ARM_SUBSCRIPTION_ID }} - ARM_TENANT_ID: ${{ secrets.TERRAFORM_ARM_TENANT_ID }} - OKTA_API_TOKEN: ${{ secrets.OKTA_API_TOKEN_NONPROD }} - steps: - - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: hashicorp/setup-terraform@v2.0.3 - with: - terraform_version: 1.3.3 - - name: Build ReportStream function app - uses: ./.github/actions/build-reportstream-functions - with: - deploy-env: ${{env.DEPLOY_ENV}} - - name: Terraform Init - run: make init-${{ env.DEPLOY_ENV }} - - name: Terraform deploy (infrastructure and staging slot) - run: make deploy-${{ env.DEPLOY_ENV }} - - name: Wait for correct commit to be deployed in staging slot - timeout-minutes: 5 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-commit - - name: Wait for staging deploy to be ready - timeout-minutes: 1 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-readiness - build_frontend: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v3.8.1 - with: - node-version: ${{env.NODE_VERSION}} - - name: Use cache for node_modules - uses: actions/cache@v3.3.2 - with: - path: | - ./frontend/node_modules - key: npm-${{env.NODE_VERSION}}-${{ hashFiles('frontend/yarn.lock', 'frontend/package.json') }} - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: ./.github/actions/build-frontend - name: Build front-end application - with: - deploy-env: ${{env.DEPLOY_ENV}} - smarty-streets-key: ${{ secrets.SMARTY_STREETS_KEY }} - client-tarball: ./client.tgz - okta-enabled: true - okta-url: https://hhs-prime.oktapreview.com - okta-client-id: 0oa5utork4AEiKyO71d7 - - name: Save compiled frontend application - uses: actions/upload-artifact@v3 - if: success() - with: - name: frontend-tarball - path: client.tgz - retention-days: 1 - deploy: - runs-on: ubuntu-latest - environment: - name: Dev5 - url: https://${{env.DEPLOY_ENV}}.simplereport.gov - needs: [prerelease_backend] - steps: - - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - name: Retrieve frontend build - uses: actions/download-artifact@v3 - with: - name: frontend-tarball - - name: Promote and deploy - uses: ./.github/actions/deploy-application - with: - client-tarball: client.tgz - deploy-env: ${{env.DEPLOY_ENV}} diff --git a/.github/workflows/deployDev6.yml b/.github/workflows/deployDev6.yml deleted file mode 100644 index f83863a1a9a..00000000000 --- a/.github/workflows/deployDev6.yml +++ /dev/null @@ -1,112 +0,0 @@ -name: Deploy Dev6 - -on: - workflow_dispatch: - -env: - DEPLOY_ENV: dev6 - NODE_VERSION: 18 -concurrency: - group: dev6-deploy - cancel-in-progress: false - -jobs: - build_docker: - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./backend - steps: - - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to ACR - run: docker login ${{ secrets.ACR_REPO_URL }} -u ${{ secrets.ACR_ADMIN_USERNAME }} -p ${{ secrets.ACR_ADMIN_PASWORD }} - - name: Build and push Docker images - run: ./build_and_push.sh - prerelease_backend: - runs-on: ubuntu-latest - needs: [build_frontend, build_docker] - defaults: - run: - working-directory: ./ops - env: # all Azure interaction is through Terraform - ARM_CLIENT_ID: ${{ secrets.TERRAFORM_ARM_CLIENT_ID }} - ARM_CLIENT_SECRET: ${{ secrets.TERRAFORM_ARM_CLIENT_SECRET }} - ARM_SUBSCRIPTION_ID: ${{ secrets.TERRAFORM_ARM_SUBSCRIPTION_ID }} - ARM_TENANT_ID: ${{ secrets.TERRAFORM_ARM_TENANT_ID }} - OKTA_API_TOKEN: ${{ secrets.OKTA_API_TOKEN_NONPROD }} - steps: - - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: hashicorp/setup-terraform@v2.0.3 - with: - terraform_version: 1.3.3 - - name: Build ReportStream function app - uses: ./.github/actions/build-reportstream-functions - with: - deploy-env: ${{env.DEPLOY_ENV}} - - name: Terraform Init - run: make init-${{ env.DEPLOY_ENV }} - - name: Terraform deploy (infrastructure and staging slot) - run: make deploy-${{ env.DEPLOY_ENV }} - - name: Wait for correct commit to be deployed in staging slot - timeout-minutes: 5 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-commit - - name: Wait for staging deploy to be ready - timeout-minutes: 1 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-readiness - build_frontend: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v3.8.1 - with: - node-version: ${{env.NODE_VERSION}} - - name: Use cache for node_modules - uses: actions/cache@v3.3.2 - with: - path: | - ./frontend/node_modules - key: npm-${{env.NODE_VERSION}}-${{ hashFiles('frontend/yarn.lock', 'frontend/package.json') }} - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: ./.github/actions/build-frontend - name: Build front-end application - with: - deploy-env: ${{env.DEPLOY_ENV}} - smarty-streets-key: ${{ secrets.SMARTY_STREETS_KEY }} - client-tarball: ./client.tgz - okta-enabled: true - okta-url: https://hhs-prime.oktapreview.com - okta-client-id: 0oa5uvg0531PLkxNP1d7 - - name: Save compiled frontend application - uses: actions/upload-artifact@v3 - if: success() - with: - name: frontend-tarball - path: client.tgz - retention-days: 1 - deploy: - runs-on: ubuntu-latest - environment: - name: dev6 - url: https://${{env.DEPLOY_ENV}}.simplereport.gov - needs: [prerelease_backend] - steps: - - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - name: Retrieve frontend build - uses: actions/download-artifact@v3 - with: - name: frontend-tarball - - name: Promote and deploy - uses: ./.github/actions/deploy-application - with: - client-tarball: client.tgz - deploy-env: ${{ env.DEPLOY_ENV }} diff --git a/.github/workflows/deployDev7.yml b/.github/workflows/deployDev7.yml deleted file mode 100644 index c13a22ce57a..00000000000 --- a/.github/workflows/deployDev7.yml +++ /dev/null @@ -1,112 +0,0 @@ -name: Deploy Dev7 - -on: - workflow_dispatch: - -env: - DEPLOY_ENV: dev7 - NODE_VERSION: 18 -concurrency: - group: dev7-deploy - cancel-in-progress: false - -jobs: - build_docker: - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./backend - steps: - - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to ACR - run: docker login ${{ secrets.ACR_REPO_URL }} -u ${{ secrets.ACR_ADMIN_USERNAME }} -p ${{ secrets.ACR_ADMIN_PASWORD }} - - name: Build and push Docker images - run: ./build_and_push.sh - prerelease_backend: - runs-on: ubuntu-latest - needs: [build_frontend, build_docker] - defaults: - run: - working-directory: ./ops - env: # all Azure interaction is through Terraform - ARM_CLIENT_ID: ${{ secrets.TERRAFORM_ARM_CLIENT_ID }} - ARM_CLIENT_SECRET: ${{ secrets.TERRAFORM_ARM_CLIENT_SECRET }} - ARM_SUBSCRIPTION_ID: ${{ secrets.TERRAFORM_ARM_SUBSCRIPTION_ID }} - ARM_TENANT_ID: ${{ secrets.TERRAFORM_ARM_TENANT_ID }} - OKTA_API_TOKEN: ${{ secrets.OKTA_API_TOKEN_NONPROD }} - steps: - - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: hashicorp/setup-terraform@v2.0.3 - with: - terraform_version: 1.3.3 - - name: Build ReportStream function app - uses: ./.github/actions/build-reportstream-functions - with: - deploy-env: ${{env.DEPLOY_ENV}} - - name: Terraform Init - run: make init-${{ env.DEPLOY_ENV }} - - name: Terraform deploy (infrastructure and staging slot) - run: make deploy-${{ env.DEPLOY_ENV }} - - name: Wait for correct commit to be deployed in staging slot - timeout-minutes: 5 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-commit - - name: Wait for staging deploy to be ready - timeout-minutes: 1 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-readiness - build_frontend: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v3.8.1 - with: - node-version: ${{env.NODE_VERSION}} - - name: Use cache for node_modules - uses: actions/cache@v3.3.2 - with: - path: | - ./frontend/node_modules - key: npm-${{env.NODE_VERSION}}-${{ hashFiles('frontend/yarn.lock', 'frontend/package.json') }} - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: ./.github/actions/build-frontend - name: Build front-end application - with: - deploy-env: ${{env.DEPLOY_ENV}} - smarty-streets-key: ${{ secrets.SMARTY_STREETS_KEY }} - client-tarball: ./client.tgz - okta-enabled: true - okta-url: https://hhs-prime.oktapreview.com - okta-client-id: 0oa5uvpmofBA83yGZ1d7 - - name: Save compiled frontend application - uses: actions/upload-artifact@v3 - if: success() - with: - name: frontend-tarball - path: client.tgz - retention-days: 1 - deploy: - runs-on: ubuntu-latest - environment: - name: dev7 - url: https://${{env.DEPLOY_ENV}}.simplereport.gov - needs: [prerelease_backend] - steps: - - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - name: Retrieve frontend build - uses: actions/download-artifact@v3 - with: - name: frontend-tarball - - name: Promote and deploy - uses: ./.github/actions/deploy-application - with: - client-tarball: client.tgz - deploy-env: ${{env.DEPLOY_ENV}} diff --git a/.github/workflows/deployPentest.yml b/.github/workflows/deployPentest.yml deleted file mode 100644 index e3d6690292d..00000000000 --- a/.github/workflows/deployPentest.yml +++ /dev/null @@ -1,108 +0,0 @@ -name: Deploy Pentest - -on: - workflow_dispatch: - -env: - DEPLOY_ENV: pentest - NODE_VERSION: 18 -concurrency: - group: pentest-deploy - cancel-in-progress: false - -jobs: - build_docker: - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./backend - steps: - - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to ACR - run: docker login ${{ secrets.ACR_REPO_URL }} -u ${{ secrets.ACR_ADMIN_USERNAME }} -p ${{ secrets.ACR_ADMIN_PASWORD }} - - name: Build and push Docker images - run: ./build_and_push.sh - prerelease_backend: - runs-on: ubuntu-latest - needs: [build_frontend, build_docker] - defaults: - run: - working-directory: ./ops - env: # all Azure interaction is through terraform - ARM_CLIENT_ID: ${{ secrets.TERRAFORM_ARM_CLIENT_ID }} - ARM_CLIENT_SECRET: ${{ secrets.TERRAFORM_ARM_CLIENT_SECRET }} - ARM_SUBSCRIPTION_ID: ${{ secrets.TERRAFORM_ARM_SUBSCRIPTION_ID }} - ARM_TENANT_ID: ${{ secrets.TERRAFORM_ARM_TENANT_ID }} - OKTA_API_TOKEN: ${{ secrets.OKTA_API_TOKEN_NONPROD }} - steps: - - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: hashicorp/setup-terraform@v2.0.3 - with: - terraform_version: 1.3.3 - - name: Terraform Init - run: make init-${{ env.DEPLOY_ENV }} - - name: Terraform deploy (infrastructure and staging slot) - run: make deploy-${{ env.DEPLOY_ENV }} - - name: Wait for correct commit to be deployed in staging slot - timeout-minutes: 5 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-commit - - name: Wait for staging deploy to be ready - timeout-minutes: 1 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-readiness - build_frontend: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v3.8.1 - with: - node-version: ${{env.NODE_VERSION}} - - name: Use cache for node_modules - uses: actions/cache@v3.3.2 - with: - path: | - ./frontend/node_modules - key: npm-${{env.NODE_VERSION}}-${{ hashFiles('frontend/yarn.lock', 'frontend/package.json') }} - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: ./.github/actions/build-frontend - name: Build front-end application - with: - deploy-env: ${{env.DEPLOY_ENV}} - smarty-streets-key: ${{ secrets.SMARTY_STREETS_KEY }} - client-tarball: ./client.tgz - okta-enabled: true - okta-url: https://hhs-prime.oktapreview.com - okta-client-id: 0oa1hwvzb4X0sgGAt1d7 - - name: Save compiled frontend application - uses: actions/upload-artifact@v3 - if: success() - with: - name: frontend-tarball - path: client.tgz - retention-days: 1 - deploy: - runs-on: ubuntu-latest - environment: - name: Pentest - url: https://pentest.simplereport.gov - needs: [prerelease_backend] - steps: - - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - name: Retrieve frontend build - uses: actions/download-artifact@v3 - with: - name: frontend-tarball - - name: Promote and deploy - uses: ./.github/actions/deploy-application - with: - client-tarball: client.tgz - deploy-env: ${{env.DEPLOY_ENV}} diff --git a/.github/workflows/deployProd.yml b/.github/workflows/deployProd.yml index 458ddcc3e7d..d703f939ddc 100644 --- a/.github/workflows/deployProd.yml +++ b/.github/workflows/deployProd.yml @@ -9,98 +9,65 @@ on: env: DEPLOY_ENV: prod NODE_VERSION: 18 + concurrency: group: prod-deploy cancel-in-progress: false jobs: build_docker: - if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest - defaults: - run: - working-directory: ./backend steps: - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to ACR - run: docker login ${{ secrets.ACR_REPO_URL }} -u ${{ secrets.ACR_ADMIN_USERNAME }} -p ${{ secrets.ACR_ADMIN_PASWORD }} - - name: Build and push Docker images - run: ./build_and_push.sh - prerelease_backend: - if: ${{ github.event.workflow_run.conclusion == 'success' }} + - name: Build and Push backend + uses: ./.github/actions/build-and-push + with: + acr_registry: ${{ secrets.ACR_REPO_URL }} + acr_username: ${{ secrets.ACR_ADMIN_USERNAME }} + acr_password: ${{ secrets.ACR_ADMIN_PASWORD }} + + build_frontend: runs-on: ubuntu-latest - needs: [build_frontend, build_docker] - defaults: - run: - working-directory: ./ops - env: # all Azure interaction is through terraform - ARM_CLIENT_ID: ${{ secrets.TERRAFORM_ARM_CLIENT_ID }} - ARM_CLIENT_SECRET: ${{ secrets.TERRAFORM_ARM_CLIENT_SECRET }} - ARM_SUBSCRIPTION_ID: ${{ secrets.TERRAFORM_ARM_SUBSCRIPTION_ID }} - ARM_TENANT_ID: ${{ secrets.TERRAFORM_ARM_TENANT_ID }} - OKTA_API_TOKEN: ${{ secrets.OKTA_API_TOKEN }} steps: - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: hashicorp/setup-terraform@v2.0.3 - with: - terraform_version: 1.3.3 - - name: Build ReportStream function app - uses: ./.github/actions/build-reportstream-functions + - uses: ./.github/actions/build-frontend + name: Build front-end application with: - deploy-env: ${{env.DEPLOY_ENV}} - - name: Terraform Init - run: make init-${{ env.DEPLOY_ENV }} - - name: Terraform deploy (infrastructure and staging slot) - run: make deploy-${{ env.DEPLOY_ENV }} - - name: Wait for correct release to be deployed in staging slot - timeout-minutes: 5 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-commit - - name: Wait for staging deploy to be ready - timeout-minutes: 1 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-readiness - build_frontend: - if: ${{ github.event.workflow_run.conclusion == 'success' }} + azure_creds: ${{ secrets.AZURE_CREDENTIALS }} + client_tarball: ./client.tgz + deploy_env: ${{env.DEPLOY_ENV}} + smarty_streets_key: ${{ secrets.SMARTY_STREETS_KEY }} + okta_enabled: true + okta_url: https://hhs-prime.okta.com + okta_client_id: 0oa5ahrdfSpxmNZO74h6 + + prerelease_backend: runs-on: ubuntu-latest - outputs: - download-url: ${{steps.upload.outputs.url}} + needs: [build_frontend, build_docker] steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3.8.1 - with: - node-version: ${{env.NODE_VERSION}} - - name: Use cache for node_modules - uses: actions/cache@v3.3.2 + - uses: ./.github/actions/tf-deploy + name: Deploy with Terraform with: - path: | - ./frontend/node_modules - key: npm-${{env.NODE_VERSION}}-${{ hashFiles('frontend/yarn.lock', 'frontend/package.json') }} - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: ./.github/actions/build-frontend - name: Build front-end application + azure_creds: ${{ secrets.AZURE_CREDENTIALS }} + deploy_env: ${{ env.DEPLOY_ENV }} + terraform_arm_client_id: ${{ secrets.TERRAFORM_ARM_CLIENT_ID }} + terraform_arm_client_secret: ${{ secrets.TERRAFORM_ARM_CLIENT_SECRET }} + terraform_arm_subscription_id: ${{ secrets.TERRAFORM_ARM_SUBSCRIPTION_ID }} + terraform_arm_tenant_id: ${{ secrets.TERRAFORM_ARM_TENANT_ID }} + okta_api_token: ${{ secrets.OKTA_API_TOKEN }} + - uses: ./.github/actions/stg-wait-for-slot-commit + name: Wait for correct commit to be deployed in staging slot + timeout-minutes: 5 with: - deploy-env: ${{env.DEPLOY_ENV}} - smarty-streets-key: ${{ secrets.SMARTY_STREETS_KEY }} - base-domain-name: www.simplereport.gov - client-tarball: ./client.tgz - okta-enabled: true - okta-url: https://hhs-prime.okta.com - okta-client-id: 0oa5ahrdfSpxmNZO74h6 - - name: Save compiled frontend application - uses: actions/upload-artifact@v3 - if: success() + deploy_env: ${{ env.DEPLOY_ENV }} + - uses: ./.github/actions/stg-wait-for-slot-readiness + name: Wait for staging deploy to be ready + timeout-minutes: 1 with: - name: frontend-tarball - path: client.tgz - retention-days: 1 + deploy_env: ${{ env.DEPLOY_ENV }} + deploy: - if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest environment: name: Production @@ -108,18 +75,13 @@ jobs: needs: [prerelease_backend] steps: - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - name: Retrieve frontend build - uses: actions/download-artifact@v3 - with: - name: frontend-tarball - name: Promote and deploy uses: ./.github/actions/deploy-application with: - client-tarball: client.tgz - deploy-env: ${{env.DEPLOY_ENV}} + azure_creds: ${{ secrets.AZURE_CREDENTIALS }} + client_tarball: client.tgz + deploy_env: ${{ env.DEPLOY_ENV }} + slack_alert: runs-on: ubuntu-latest if: failure() diff --git a/.github/workflows/deployStg.yml b/.github/workflows/deployStg.yml index 9d21fd7e10d..105705a7689 100644 --- a/.github/workflows/deployStg.yml +++ b/.github/workflows/deployStg.yml @@ -5,123 +5,82 @@ on: branches: - main -# on: -# workflow_run: -# workflows: ["Deploy Test"] -# types: -# - completed - env: DEPLOY_ENV: stg NODE_VERSION: 18 + concurrency: group: stg-deploy cancel-in-progress: false jobs: build_docker: - # if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest - defaults: - run: - working-directory: ./backend steps: - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to ACR - run: docker login ${{ secrets.ACR_REPO_URL }} -u ${{ secrets.ACR_ADMIN_USERNAME }} -p ${{ secrets.ACR_ADMIN_PASWORD }} - - name: Build and push Docker images - run: ./build_and_push.sh - prerelease_backend: - # if: ${{ github.event.workflow_run.conclusion == 'success' }} + - name: Build and Push backend + uses: ./.github/actions/build-and-push + with: + acr_registry: ${{ secrets.ACR_REPO_URL }} + acr_username: ${{ secrets.ACR_ADMIN_USERNAME }} + acr_password: ${{ secrets.ACR_ADMIN_PASWORD }} + + build_frontend: runs-on: ubuntu-latest - needs: [build_frontend, build_docker] - defaults: - run: - working-directory: ./ops - env: # all Azure interaction is through terraform - ARM_CLIENT_ID: ${{ secrets.TERRAFORM_ARM_CLIENT_ID }} - ARM_CLIENT_SECRET: ${{ secrets.TERRAFORM_ARM_CLIENT_SECRET }} - ARM_SUBSCRIPTION_ID: ${{ secrets.TERRAFORM_ARM_SUBSCRIPTION_ID }} - ARM_TENANT_ID: ${{ secrets.TERRAFORM_ARM_TENANT_ID }} - OKTA_API_TOKEN: ${{ secrets.OKTA_API_TOKEN }} steps: - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: hashicorp/setup-terraform@v2.0.3 - with: - terraform_version: 1.3.3 - - name: Build ReportStream function app - uses: ./.github/actions/build-reportstream-functions + - uses: ./.github/actions/build-frontend + name: Build front-end application with: - deploy-env: ${{env.DEPLOY_ENV}} - - name: Terraform Init - run: make init-${{ env.DEPLOY_ENV }} - - name: Terraform deploy (infrastructure and staging slot) - run: make deploy-${{ env.DEPLOY_ENV }} - - name: Wait for correct release to be deployed in staging slot - timeout-minutes: 5 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-commit - - name: Wait for staging deploy to be ready - timeout-minutes: 1 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-readiness - build_frontend: - # if: ${{ github.event.workflow_run.conclusion == 'success' }} + azure_creds: ${{ secrets.AZURE_CREDENTIALS }} + client_tarball: ./client.tgz + deploy_env: ${{env.DEPLOY_ENV}} + smarty_streets_key: ${{ secrets.SMARTY_STREETS_KEY }} + okta_enabled: true + okta_url: https://hhs-prime.okta.com + okta_client_id: 0oa62qncijWSeQMuc4h6 + + prerelease_backend: runs-on: ubuntu-latest + needs: [build_frontend, build_docker] steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3.8.1 - with: - node-version: ${{env.NODE_VERSION}} - - name: Use cache for node_modules - uses: actions/cache@v3.3.2 + - uses: ./.github/actions/tf-deploy + name: Deploy with Terraform with: - path: | - ./frontend/node_modules - key: npm-${{env.NODE_VERSION}}-${{ hashFiles('frontend/yarn.lock', 'frontend/package.json') }} - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: ./.github/actions/build-frontend - name: Build front-end application + azure_creds: ${{ secrets.AZURE_CREDENTIALS }} + deploy_env: ${{ env.DEPLOY_ENV }} + terraform_arm_client_id: ${{ secrets.TERRAFORM_ARM_CLIENT_ID }} + terraform_arm_client_secret: ${{ secrets.TERRAFORM_ARM_CLIENT_SECRET }} + terraform_arm_subscription_id: ${{ secrets.TERRAFORM_ARM_SUBSCRIPTION_ID }} + terraform_arm_tenant_id: ${{ secrets.TERRAFORM_ARM_TENANT_ID }} + okta_api_token: ${{ secrets.OKTA_API_TOKEN }} + - uses: ./.github/actions/stg-wait-for-slot-commit + name: Wait for correct commit to be deployed in staging slot + timeout-minutes: 5 with: - deploy-env: ${{env.DEPLOY_ENV}} - smarty-streets-key: ${{ secrets.SMARTY_STREETS_KEY }} - client-tarball: ./client.tgz - okta-enabled: true - okta-url: https://hhs-prime.okta.com - okta-client-id: 0oa62qncijWSeQMuc4h6 - - name: Save compiled frontend application - uses: actions/upload-artifact@v3 - if: success() + deploy_env: ${{ env.DEPLOY_ENV }} + - uses: ./.github/actions/stg-wait-for-slot-readiness + name: Wait for staging deploy to be ready + timeout-minutes: 1 with: - name: frontend-tarball - path: client.tgz - retention-days: 1 + deploy_env: ${{ env.DEPLOY_ENV }} + deploy: - # if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest environment: - name: Staging - url: https://stg.simplereport.gov + name: ${{ env.DEPLOY_ENV }} + url: https://${{ env.DEPLOY_ENV }}.simplereport.gov needs: [prerelease_backend] steps: - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - name: Retrieve frontend build - uses: actions/download-artifact@v3 - with: - name: frontend-tarball - name: Promote and deploy uses: ./.github/actions/deploy-application with: - client-tarball: client.tgz - deploy-env: ${{env.DEPLOY_ENV}} + azure_creds: ${{ secrets.AZURE_CREDENTIALS }} + client_tarball: client.tgz + deploy_env: ${{ env.DEPLOY_ENV }} + slack_alert: runs-on: ubuntu-latest if: failure() diff --git a/.github/workflows/deployTest.yml b/.github/workflows/deployTest.yml index 8b8f7f213d3..53a6ece0d46 100644 --- a/.github/workflows/deployTest.yml +++ b/.github/workflows/deployTest.yml @@ -8,6 +8,7 @@ on: env: DEPLOY_ENV: test NODE_VERSION: 18 + concurrency: group: test-deploy cancel-in-progress: false @@ -15,103 +16,71 @@ concurrency: jobs: build_docker: runs-on: ubuntu-latest - defaults: - run: - working-directory: ./backend steps: - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to ACR - run: docker login ${{ secrets.ACR_REPO_URL }} -u ${{ secrets.ACR_ADMIN_USERNAME }} -p ${{ secrets.ACR_ADMIN_PASWORD }} - - name: Build and push Docker images - run: ./build_and_push.sh - prerelease_backend: + - name: Build and Push backend + uses: ./.github/actions/build-and-push + with: + acr_registry: ${{ secrets.ACR_REPO_URL }} + acr_username: ${{ secrets.ACR_ADMIN_USERNAME }} + acr_password: ${{ secrets.ACR_ADMIN_PASWORD }} + + build_frontend: runs-on: ubuntu-latest - needs: [build_frontend, build_docker] - defaults: - run: - working-directory: ./ops - env: # all Azure interaction is through terraform - ARM_CLIENT_ID: ${{ secrets.TERRAFORM_ARM_CLIENT_ID }} - ARM_CLIENT_SECRET: ${{ secrets.TERRAFORM_ARM_CLIENT_SECRET }} - ARM_SUBSCRIPTION_ID: ${{ secrets.TERRAFORM_ARM_SUBSCRIPTION_ID }} - ARM_TENANT_ID: ${{ secrets.TERRAFORM_ARM_TENANT_ID }} - OKTA_API_TOKEN: ${{ secrets.OKTA_API_TOKEN_NONPROD }} steps: - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: hashicorp/setup-terraform@v2.0.3 - with: - terraform_version: 1.3.3 - - name: Build ReportStream function app - uses: ./.github/actions/build-reportstream-functions + - uses: ./.github/actions/build-frontend + name: Build front-end application with: - deploy-env: ${{env.DEPLOY_ENV}} - - name: Terraform Init - run: make init-${{ env.DEPLOY_ENV }} - - name: Terraform deploy (infrastructure and staging slot) - run: make deploy-${{ env.DEPLOY_ENV }} - - name: Wait for correct commit to be deployed in staging slot - timeout-minutes: 5 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-commit - - name: Wait for staging deploy to be ready - timeout-minutes: 1 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-readiness - build_frontend: + azure_creds: ${{ secrets.AZURE_CREDENTIALS }} + client_tarball: ./client.tgz + deploy_env: ${{env.DEPLOY_ENV}} + smarty_streets_key: ${{ secrets.SMARTY_STREETS_KEY }} + okta_enabled: true + okta_url: https://hhs-prime.oktapreview.com + okta_client_id: 0oa1khettjHnj3EPT1d7 + + prerelease_backend: runs-on: ubuntu-latest + needs: [build_frontend, build_docker] steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3.8.1 - with: - node-version: ${{env.NODE_VERSION}} - - name: Use cache for node_modules - uses: actions/cache@v3.3.2 + - uses: ./.github/actions/tf-deploy + name: Deploy with Terraform with: - path: | - ./frontend/node_modules - key: npm-${{env.NODE_VERSION}}-${{ hashFiles('frontend/yarn.lock', 'frontend/package.json') }} - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: ./.github/actions/build-frontend - name: Build front-end application + azure_creds: ${{ secrets.AZURE_CREDENTIALS }} + deploy_env: ${{ env.DEPLOY_ENV }} + terraform_arm_client_id: ${{ secrets.TERRAFORM_ARM_CLIENT_ID }} + terraform_arm_client_secret: ${{ secrets.TERRAFORM_ARM_CLIENT_SECRET }} + terraform_arm_subscription_id: ${{ secrets.TERRAFORM_ARM_SUBSCRIPTION_ID }} + terraform_arm_tenant_id: ${{ secrets.TERRAFORM_ARM_TENANT_ID }} + okta_api_token: ${{ secrets.OKTA_API_TOKEN_NONPROD }} + - uses: ./.github/actions/stg-wait-for-slot-commit + name: Wait for correct commit to be deployed in staging slot + timeout-minutes: 5 with: - deploy-env: ${{env.DEPLOY_ENV}} - smarty-streets-key: ${{ secrets.SMARTY_STREETS_KEY }} - client-tarball: ./client.tgz - okta-enabled: true - okta-url: https://hhs-prime.oktapreview.com - okta-client-id: 0oa1khettjHnj3EPT1d7 - - name: Save compiled frontend application - uses: actions/upload-artifact@v3 - if: success() + deploy_env: ${{ env.DEPLOY_ENV }} + - uses: ./.github/actions/stg-wait-for-slot-readiness + name: Wait for staging deploy to be ready + timeout-minutes: 1 with: - name: frontend-tarball - path: client.tgz - retention-days: 1 + deploy_env: ${{ env.DEPLOY_ENV }} + deploy: runs-on: ubuntu-latest environment: - name: Test - url: https://test.simplereport.gov + name: ${{ env.DEPLOY_ENV }} + url: https://${{ env.DEPLOY_ENV }}.simplereport.gov needs: [prerelease_backend] steps: - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - name: Retrieve frontend build - uses: actions/download-artifact@v3 - with: - name: frontend-tarball - name: Promote and deploy uses: ./.github/actions/deploy-application with: - client-tarball: client.tgz - deploy-env: ${{env.DEPLOY_ENV}} + azure_creds: ${{ secrets.AZURE_CREDENTIALS }} + client_tarball: client.tgz + deploy_env: ${{ env.DEPLOY_ENV }} + slack_alert: runs-on: ubuntu-latest if: failure() diff --git a/.github/workflows/deployTraining.yml b/.github/workflows/deployTraining.yml index 6b4cc8dcedf..73690e42be5 100644 --- a/.github/workflows/deployTraining.yml +++ b/.github/workflows/deployTraining.yml @@ -9,109 +9,77 @@ on: env: DEPLOY_ENV: training NODE_VERSION: 18 + concurrency: group: training-deploy cancel-in-progress: false jobs: build_docker: - if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest - defaults: - run: - working-directory: ./backend steps: - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to ACR - run: docker login ${{ secrets.ACR_REPO_URL }} -u ${{ secrets.ACR_ADMIN_USERNAME }} -p ${{ secrets.ACR_ADMIN_PASWORD }} - - name: Build and push Docker images - run: ./build_and_push.sh - prerelease_backend: - if: ${{ github.event.workflow_run.conclusion == 'success' }} + - name: Build and Push backend + uses: ./.github/actions/build-and-push + with: + acr_registry: ${{ secrets.ACR_REPO_URL }} + acr_username: ${{ secrets.ACR_ADMIN_USERNAME }} + acr_password: ${{ secrets.ACR_ADMIN_PASWORD }} + + build_frontend: runs-on: ubuntu-latest - needs: [build_frontend, build_docker] - defaults: - run: - working-directory: ./ops - env: # all Azure interaction is through terraform - ARM_CLIENT_ID: ${{ secrets.TERRAFORM_ARM_CLIENT_ID }} - ARM_CLIENT_SECRET: ${{ secrets.TERRAFORM_ARM_CLIENT_SECRET }} - ARM_SUBSCRIPTION_ID: ${{ secrets.TERRAFORM_ARM_SUBSCRIPTION_ID }} - ARM_TENANT_ID: ${{ secrets.TERRAFORM_ARM_TENANT_ID }} - OKTA_API_TOKEN: ${{ secrets.OKTA_API_TOKEN }} steps: - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: hashicorp/setup-terraform@v2.0.3 + - uses: ./.github/actions/build-frontend + name: Build front-end application with: - terraform_version: 1.3.3 - - name: Terraform Init - run: make init-${{ env.DEPLOY_ENV }} - - name: Terraform deploy (infrastructure and staging slot) - run: make deploy-${{ env.DEPLOY_ENV }} - - name: Wait for correct commit to be deployed in staging slot - timeout-minutes: 5 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-commit - - name: Wait for staging deploy to be ready - timeout-minutes: 1 - run: make wait-for-${{ env.DEPLOY_ENV }}-slot-readiness - build_frontend: - if: ${{ github.event.workflow_run.conclusion == 'success' }} + azure_creds: ${{ secrets.AZURE_CREDENTIALS }} + client_tarball: ./client.tgz + deploy_env: ${{env.DEPLOY_ENV}} + smarty_streets_key: ${{ secrets.SMARTY_STREETS_KEY }} + okta_enabled: false + + prerelease_backend: runs-on: ubuntu-latest + needs: [build_frontend, build_docker] steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3.8.1 - with: - node-version: ${{env.NODE_VERSION}} - - name: Use cache for node_modules - uses: actions/cache@v3.3.2 + - uses: ./.github/actions/tf-deploy + name: Deploy with Terraform with: - path: | - ./frontend/node_modules - key: npm-${{env.NODE_VERSION}}-${{ hashFiles('frontend/yarn.lock', 'frontend/package.json') }} - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - uses: ./.github/actions/build-frontend - name: Build front-end application + azure_creds: ${{ secrets.AZURE_CREDENTIALS }} + deploy_env: ${{ env.DEPLOY_ENV }} + terraform_arm_client_id: ${{ secrets.TERRAFORM_ARM_CLIENT_ID }} + terraform_arm_client_secret: ${{ secrets.TERRAFORM_ARM_CLIENT_SECRET }} + terraform_arm_subscription_id: ${{ secrets.TERRAFORM_ARM_SUBSCRIPTION_ID }} + terraform_arm_tenant_id: ${{ secrets.TERRAFORM_ARM_TENANT_ID }} + okta_api_token: ${{ secrets.OKTA_API_TOKEN }} + - uses: ./.github/actions/stg-wait-for-slot-commit + name: Wait for correct commit to be deployed in staging slot + timeout-minutes: 5 with: - deploy-env: ${{env.DEPLOY_ENV}} - smarty-streets-key: ${{ secrets.SMARTY_STREETS_KEY }} - client-tarball: ./client.tgz - is-training-site: true - okta-enabled: false - - name: Save compiled frontend application - uses: actions/upload-artifact@v3 - if: success() + deploy_env: ${{ env.DEPLOY_ENV }} + - uses: ./.github/actions/stg-wait-for-slot-readiness + name: Wait for staging deploy to be ready + timeout-minutes: 1 with: - name: frontend-tarball - path: client.tgz - retention-days: 1 + deploy_env: ${{ env.DEPLOY_ENV }} + deploy: - if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest environment: - name: Training - url: https://training.simplereport.gov + name: ${{ env.DEPLOY_ENV }} + url: https://${{ env.DEPLOY_ENV }}.simplereport.gov needs: [prerelease_backend] steps: - uses: actions/checkout@v4 - - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - name: Retrieve frontend build - uses: actions/download-artifact@v3 - with: - name: frontend-tarball - name: Promote and deploy uses: ./.github/actions/deploy-application with: - client-tarball: client.tgz - deploy-env: ${{env.DEPLOY_ENV}} + azure_creds: ${{ secrets.AZURE_CREDENTIALS }} + client_tarball: client.tgz + deploy_env: ${{ env.DEPLOY_ENV }} + slack_alert: runs-on: ubuntu-latest if: failure() diff --git a/.github/workflows/testingWorkflow.yml b/.github/workflows/testingWorkflow.yml index 73cf7cf18ba..a19cfd20a9b 100644 --- a/.github/workflows/testingWorkflow.yml +++ b/.github/workflows/testingWorkflow.yml @@ -25,8 +25,12 @@ permissions: packages: write jobs: - # Check for changes in the backend, cypress, database, frontend, and nginx directories + workflow_changes: + with: + what_to_check: ./.github + uses: ./.github/workflows/checkForChanges.yml + backend_changes: with: what_to_check: ./backend @@ -54,9 +58,10 @@ jobs: # Build Docker Images for the backend, cypress, database, frontend, and nginx build_backend_image: - if: needs.backend_changes.outputs.has_changes == 'true' || inputs.force_build == 'true' || github.ref == 'refs/heads/main' + if: needs.workflow_changes.outputs.has_changes == 'true' || needs.backend_changes.outputs.has_changes == 'true' || inputs.force_build == 'true' || github.ref == 'refs/heads/main' needs: - backend_changes + - workflow_changes runs-on: ubuntu-latest strategy: fail-fast: false @@ -69,23 +74,19 @@ jobs: - uses: actions/checkout@v3 - name: Build Backend Image id: set_backend_version - uses: ./.github/actions/build-docker-image + uses: ./.github/actions/docker-buildx with: - context: ./ file: ./backend/Dockerfile - acr_password: ${{ secrets.ACR_ADMIN_PASWORD }} - acr_registry: ${{ secrets.ACR_REPO_URL }} - acr_username: ${{ secrets.ACR_ADMIN_USERNAME }} - gh_registry: ghcr.io gh_username: ${{ github.actor }} gh_token: ${{ secrets.GITHUB_TOKEN }} image_name: backend platform: ${{ matrix.platform }} build_cypress_image: - if: needs.cypress_changes.outputs.has_changes == 'true' || inputs.force_build == 'true' || github.ref == 'refs/heads/main' + if: needs.workflow_changes.outputs.has_changes == 'true' || needs.cypress_changes.outputs.has_changes == 'true' || inputs.force_build == 'true' || github.ref == 'refs/heads/main' needs: - cypress_changes + - workflow_changes runs-on: ubuntu-latest strategy: fail-fast: false @@ -98,23 +99,19 @@ jobs: - uses: actions/checkout@v3 - name: Build Cypress Image id: set_cypress_version - uses: ./.github/actions/build-docker-image + uses: ./.github/actions/docker-buildx with: - context: ./ file: ./cypress/Dockerfile - acr_password: ${{ secrets.ACR_ADMIN_PASWORD }} - acr_registry: ${{ secrets.ACR_REPO_URL }} - acr_username: ${{ secrets.ACR_ADMIN_USERNAME }} - gh_registry: ghcr.io gh_username: ${{ github.actor }} gh_token: ${{ secrets.GITHUB_TOKEN }} image_name: cypress platform: ${{ matrix.platform }} build_database_image: - if: needs.database_changes.outputs.has_changes == 'true' || inputs.force_build == 'true' || github.ref == 'refs/heads/main' + if: needs.workflow_changes.outputs.has_changes == 'true' || needs.database_changes.outputs.has_changes == 'true' || inputs.force_build == 'true' || github.ref == 'refs/heads/main' needs: - database_changes + - workflow_changes runs-on: ubuntu-latest strategy: fail-fast: false @@ -127,22 +124,19 @@ jobs: - uses: actions/checkout@v3 - name: Build Database Image id: set_database_version - uses: ./.github/actions/build-docker-image + uses: ./.github/actions/docker-buildx with: context: ./backend/db-setup - acr_password: ${{ secrets.ACR_ADMIN_PASWORD }} - acr_registry: ${{ secrets.ACR_REPO_URL }} - acr_username: ${{ secrets.ACR_ADMIN_USERNAME }} - gh_registry: ghcr.io gh_username: ${{ github.actor }} gh_token: ${{ secrets.GITHUB_TOKEN }} image_name: database platform: ${{ matrix.platform }} build_frontend_image: - if: needs.frontend_changes.outputs.has_changes == 'true' || inputs.force_build == 'true' || github.ref == 'refs/heads/main' + if: needs.workflow_changes.outputs.has_changes == 'true' || needs.frontend_changes.outputs.has_changes == 'true' || inputs.force_build == 'true' || github.ref == 'refs/heads/main' needs: - frontend_changes + - workflow_changes runs-on: ubuntu-latest strategy: fail-fast: false @@ -155,14 +149,9 @@ jobs: - uses: actions/checkout@v3 - name: Build Frontend Image id: set_frontend_version - uses: ./.github/actions/build-docker-image + uses: ./.github/actions/docker-buildx with: - context: ./ file: ./frontend/Dockerfile - acr_password: ${{ secrets.ACR_ADMIN_PASWORD }} - acr_registry: ${{ secrets.ACR_REPO_URL }} - acr_username: ${{ secrets.ACR_ADMIN_USERNAME }} - gh_registry: ghcr.io gh_username: ${{ github.actor }} gh_token: ${{ secrets.GITHUB_TOKEN }} image_name: frontend @@ -177,9 +166,10 @@ jobs: "REACT_APP_DISABLE_MAINTENANCE_BANNER=true" build_frontend_lighthouse_image: - if: needs.frontend_changes.outputs.has_changes == 'true' || inputs.force_build == 'true' || github.ref == 'refs/heads/main' + if: needs.workflow_changes.outputs.has_changes == 'true' || needs.frontend_changes.outputs.has_changes == 'true' || inputs.force_build == 'true' || github.ref == 'refs/heads/main' needs: - frontend_changes + - workflow_changes runs-on: ubuntu-latest strategy: fail-fast: false @@ -192,14 +182,9 @@ jobs: - uses: actions/checkout@v3 - name: Build Frontend Lighthouse Image id: set_frontend_lighthouse_version - uses: ./.github/actions/build-docker-image + uses: ./.github/actions/docker-buildx with: - context: ./ file: ./frontend/Dockerfile - acr_password: ${{ secrets.ACR_ADMIN_PASWORD }} - acr_registry: ${{ secrets.ACR_REPO_URL }} - acr_username: ${{ secrets.ACR_ADMIN_USERNAME }} - gh_registry: ghcr.io gh_username: ${{ github.actor }} gh_token: ${{ secrets.GITHUB_TOKEN }} image_name: frontend-lighthouse @@ -212,9 +197,10 @@ jobs: "REACT_APP_DISABLE_MAINTENANCE_BANNER=true" build_nginx_image: - if: needs.nginx_changes.outputs.has_changes == 'true' || inputs.force_build == 'true' || github.ref == 'refs/heads/main' + if: needs.workflow_changes.outputs.has_changes == 'true' || needs.nginx_changes.outputs.has_changes == 'true' || inputs.force_build == 'true' || github.ref == 'refs/heads/main' needs: - nginx_changes + - workflow_changes runs-on: ubuntu-latest strategy: fail-fast: false @@ -227,13 +213,9 @@ jobs: - uses: actions/checkout@v3 - name: Build Nginx Image id: set_nginx_version - uses: ./.github/actions/build-docker-image + uses: ./.github/actions/docker-buildx with: context: ./nginx - acr_password: ${{ secrets.ACR_ADMIN_PASWORD }} - acr_registry: ${{ secrets.ACR_REPO_URL }} - acr_username: ${{ secrets.ACR_ADMIN_USERNAME }} - gh_registry: ghcr.io gh_username: ${{ github.actor }} gh_token: ${{ secrets.GITHUB_TOKEN }} image_name: nginx diff --git a/backend/build.gradle b/backend/build.gradle index b9780872b3d..2d3f06ec08d 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -84,7 +84,7 @@ dependencies { runtimeOnly 'org.springframework.session:spring-session-jdbc' // App insights instrumentation - implementation 'com.microsoft.azure:applicationinsights-core:3.4.16' + implementation 'com.microsoft.azure:applicationinsights-core:3.4.17' // Twilio for SMS implementation group: "com.twilio.sdk", name: "twilio", version: "9.9.1" diff --git a/backend/gradle.lockfile b/backend/gradle.lockfile index fac1b2cb3b6..e409d790d83 100644 --- a/backend/gradle.lockfile +++ b/backend/gradle.lockfile @@ -44,7 +44,7 @@ com.graphql-java:graphql-java-extended-validation:18.1-hibernate-validator-6.2.0 com.graphql-java:graphql-java:18.5=compileClasspath,runtimeClasspath com.graphql-java:java-dataloader:3.1.2=compileClasspath,runtimeClasspath com.ibm.icu:icu4j:72.1=compileClasspath,runtimeClasspath -com.microsoft.azure:applicationinsights-core:3.4.16=compileClasspath,runtimeClasspath +com.microsoft.azure:applicationinsights-core:3.4.17=compileClasspath,runtimeClasspath com.nimbusds:content-type:2.2=compileClasspath,runtimeClasspath com.nimbusds:lang-tag:1.6=compileClasspath,runtimeClasspath com.nimbusds:nimbus-jose-jwt:9.22=compileClasspath,runtimeClasspath diff --git a/backend/src/test/java/gov/cdc/usds/simplereport/service/DiseaseCacheServiceTest.java b/backend/src/test/java/gov/cdc/usds/simplereport/service/DiseaseCacheServiceTest.java index e3eb8c77ba8..8622ab235d4 100644 --- a/backend/src/test/java/gov/cdc/usds/simplereport/service/DiseaseCacheServiceTest.java +++ b/backend/src/test/java/gov/cdc/usds/simplereport/service/DiseaseCacheServiceTest.java @@ -34,7 +34,7 @@ void getDiseaseCacheWhenCachePopulated_successful() { Map serviceMap = _service.getKnownSupportedDiseasesMap(); List repoList = repo.findAll(); - assertThat(serviceMap).isNotNull().hasSize(4); + assertThat(serviceMap).isNotNull().isNotEmpty(); repoList.forEach( d -> { @@ -50,7 +50,7 @@ void getDiseaseCacheWhenCacheNotPopulated_successful() { Map serviceMap = _service.getKnownSupportedDiseasesMap(); List repoList = repo.findAll(); - assertThat(serviceMap).isNotNull().hasSize(4); + assertThat(serviceMap).isNotNull().isNotEmpty(); repoList.forEach( d -> { diff --git a/backend/src/test/java/gov/cdc/usds/simplereport/service/DiseaseServiceTest.java b/backend/src/test/java/gov/cdc/usds/simplereport/service/DiseaseServiceTest.java index 55928d7f5d7..afc26533b93 100644 --- a/backend/src/test/java/gov/cdc/usds/simplereport/service/DiseaseServiceTest.java +++ b/backend/src/test/java/gov/cdc/usds/simplereport/service/DiseaseServiceTest.java @@ -73,7 +73,7 @@ void getSupportedDiseasesMap_successful() { assertThat(supportedDiseasesMap) .isNotNull() - .hasSize(4) + .isNotEmpty() .containsEntry(_service.covid().getInternalId(), _service.covid()) .containsEntry(_service.fluA().getInternalId(), _service.fluA()) .containsEntry(_service.fluB().getInternalId(), _service.fluB()) @@ -86,7 +86,7 @@ void getSupportedDiseasesList_successful() { assertThat(supportedDiseasesList) .isNotNull() - .hasSize(4) + .isNotEmpty() .contains(_service.covid()) .contains(_service.fluA()) .contains(_service.fluB()) diff --git a/cypress/package.json b/cypress/package.json index 7010d0c4b6b..dbcce8c7062 100644 --- a/cypress/package.json +++ b/cypress/package.json @@ -6,7 +6,7 @@ "@faker-js/faker": "^8.0.2", "cypress": "^11.0.0", "cypress-localstorage-commands": "^2.2.4", - "dayjs": "^1.11.9", + "dayjs": "^1.11.10", "eslint": "^8.50.0", "eslint-plugin-cypress": "^2.14.0", "otplib": "^12.0.1", diff --git a/cypress/yarn.lock b/cypress/yarn.lock index 2d0391758f2..c19bb00d2c5 100644 --- a/cypress/yarn.lock +++ b/cypress/yarn.lock @@ -523,15 +523,10 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -dayjs@^1.10.4: - version "1.11.0" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.0.tgz#009bf7ef2e2ea2d5db2e6583d2d39a4b5061e805" - integrity sha512-JLC809s6Y948/FuCZPm5IX8rRhQwOiyMb2TfVVQEixG7P8Lm/gt5S7yoQZmC8x1UehI9Pb7sksEt4xx14m+7Ug== - -dayjs@^1.11.9: - version "1.11.9" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.9.tgz#9ca491933fadd0a60a2c19f6c237c03517d71d1a" - integrity sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA== +dayjs@^1.10.4, dayjs@^1.11.10: + version "1.11.10" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0" + integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ== debug@^3.1.0: version "3.2.7" diff --git a/docker-compose.yml b/docker-compose.yml index 370abdbbfa8..c4f6a2f1b01 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ services: db: build: context: backend/db-setup - image: ghcr.io/cdcgov/prime-simplereport/db:${DOCKER_DATABASE_IMAGE_VERSION:-latest} + image: ghcr.io/cdcgov/prime-simplereport/database:${DOCKER_DATABASE_IMAGE_VERSION:-latest} container_name: "${DB_NAME:-db}" environment: POSTGRES_PASSWORD: admin_password_for_local_dev_is_not_very_secure diff --git a/frontend/src/app/constants/constants.d.ts b/frontend/src/app/constants/constants.d.ts index 9d77ae3c56d..84efbf74281 100644 --- a/frontend/src/app/constants/constants.d.ts +++ b/frontend/src/app/constants/constants.d.ts @@ -605,6 +605,7 @@ interface PersonUpdate extends Address { country: string; emails: string[]; preferredLanguage: Language | null; + notes: string | null; testResultDelivery: TestResultDeliveryPreference | null; } diff --git a/frontend/src/app/patients/AddPatient.test.tsx b/frontend/src/app/patients/AddPatient.test.tsx index fcf525d609c..61ae6eb230a 100644 --- a/frontend/src/app/patients/AddPatient.test.tsx +++ b/frontend/src/app/patients/AddPatient.test.tsx @@ -1,149 +1,18 @@ -import { render, screen, within, waitFor } from "@testing-library/react"; -import { MockedProvider } from "@apollo/client/testing"; -import { Provider } from "react-redux"; -import configureStore from "redux-mock-store"; -import { MemoryRouter, Route, Routes, useLocation } from "react-router-dom"; -import userEvent, { UserEvent } from "@testing-library/user-event"; +import { screen } from "@testing-library/react"; import * as smartyStreets from "../utils/smartyStreets"; -import SRToastContainer from "../commonComponents/SRToastContainer"; import { PATIENT_TERM } from "../../config/constants"; -import AddPatient, { ADD_PATIENT, PATIENT_EXISTS } from "./AddPatient"; - -interface LocationOptions { - search: string; - pathname: string; - state: { - patientId: string; - }; -} - -const mockFacilityID = "b0d2041f-93c9-4192-b19a-dd99c0044a7e"; -const mockStore = configureStore([]); -const store = mockStore({ - facilities: [{ id: mockFacilityID, name: "123" }], - organization: { name: "Test Organization" }, -}); - -const RouterWithFacility: React.FC = ({ - children, -}) => ( - - {children} - -); - -const fillOutForm = async ( - user: UserEvent, - inputs: { [label: string]: string }, - dropdowns: { [label: string]: string }, - inputGroups: { - [legend: string]: { label: string; value: string; exact?: boolean }; - } -) => { - const inputElements = Object.entries(inputs); - - for (let i = 0; i < inputElements.length; i++) { - await user.type( - screen.getByLabelText(inputElements[i][0], { - exact: false, - }), - inputElements[i][1] - ); - } - - const dropDownElements = Object.entries(dropdowns); - - for (let i = 0; i < dropDownElements.length; i++) { - await user.selectOptions( - screen.getByLabelText(dropDownElements[i][0], { - exact: false, - }), - dropDownElements[i][1] - ); - } - - const inputGroupsElements = Object.entries(inputGroups); - - for (let i = 0; i < inputGroupsElements.length; i++) { - const [legend, { label, exact }] = inputGroupsElements[i]; - const fieldset = screen - .getByText(legend, { - exact: true, - }) - .closest("fieldset"); - if (fieldset === null) { - throw Error(`Unable to corresponding fieldset for ${legend}`); - } - await user.click( - within(fieldset).getByLabelText(label, { - exact: exact || false, - }) - ); - } -}; - -const addPatientRequestParams = { - firstName: "Alice", - middleName: null, - lastName: "Hamilton", - lookupId: null, - birthDate: "1970-09-22", - street: "25 Shattuck St", - streetTwo: null, - city: "Boston", - state: "MA", - zipCode: "02115", - country: "USA", - telephone: null, - phoneNumbers: [ - { - type: "MOBILE", - number: "617-432-1000", - }, - ], - role: null, - emails: ["foo@bar.org"], - county: "", - race: "other", - ethnicity: "refused", - gender: "female", - genderIdentity: "female", - facilityId: mockFacilityID, - preferredLanguage: null, - testResultDelivery: "SMS", -}; - -const addPatientRequestNoDelivery = { - ...addPatientRequestParams, - testResultDelivery: null, -}; - -const addPatientRequestNoAddressValidation = { - ...addPatientRequestParams, - county: null, -}; +import { + mockFacilityID, + renderWithUserNoFacility, + renderWithUserWithFacility, +} from "./AddPatientTestUtils"; describe("AddPatient", () => { describe("No facility selected", () => { - const renderWithUser = () => ({ - user: userEvent.setup(), - ...render( - - - - - } /> - - - - - ), - }); - it("does not show the form title", () => { - renderWithUser(); + renderWithUserNoFacility(); expect( screen.queryByText(`Add new ${PATIENT_TERM}`, { exact: false, @@ -151,7 +20,7 @@ describe("AddPatient", () => { ).not.toBeInTheDocument(); }); it("shows a 'No facility selected' message", async () => { - renderWithUser(); + renderWithUserNoFacility(); expect( screen.getByText("No facility selected", { exact: false, @@ -161,129 +30,16 @@ describe("AddPatient", () => { }); describe("happy path", () => { - let zipCodeSpy: jest.SpyInstance; - beforeEach(async () => { jest.spyOn(smartyStreets, "getBestSuggestion").mockImplementation(); jest .spyOn(smartyStreets, "suggestionIsCloseEnough") .mockReturnValue(false); jest.spyOn(smartyStreets, "getZipCodeData").mockResolvedValue(undefined); - zipCodeSpy = jest - .spyOn(smartyStreets, "isValidZipCodeForState") - .mockReturnValue(true); - }); - const mocks = [ - { - request: { - query: PATIENT_EXISTS, - variables: { - firstName: "Alice", - lastName: "Hamilton", - birthDate: "1970-09-22", - facilityId: "b0d2041f-93c9-4192-b19a-dd99c0044a7e", - }, - }, - result: { - data: { - patientExistsWithoutZip: false, - }, - }, - }, - { - request: { - query: PATIENT_EXISTS, - variables: { - firstName: "Alice", - lastName: "Hamilton", - birthDate: "1970-09-22", - facilityId: "", - }, - }, - result: { - data: { - patientExistsWithoutZip: false, - }, - }, - }, - { - request: { - query: ADD_PATIENT, - variables: addPatientRequestParams, - }, - result: { - data: { - addPatient: { - internalId: "153f661f-b6ea-4711-b9ab-487b95198cce", - facility: { - id: "facility-id-001", - }, - }, - }, - }, - }, - { - request: { - query: ADD_PATIENT, - variables: addPatientRequestNoDelivery, - }, - result: { - data: { - addPatient: { - internalId: "153f661f-b6ea-4711-b9ab-487b95198cce", - facility: { - id: "facility-id-001", - }, - }, - }, - }, - }, - { - request: { - query: ADD_PATIENT, - variables: addPatientRequestNoAddressValidation, - }, - result: { - data: { - addPatient: { - internalId: "153f661f-b6ea-4711-b9ab-487b95198cce", - facility: { - id: "facility-id-001", - }, - }, - }, - }, - }, - ]; - const Queue = () => { - const location = useLocation() as LocationOptions; - - return ( -

- Testing Queue! {location.search} {location.state.patientId} -

- ); - }; - const renderWithUser = () => ({ - user: userEvent.setup(), - ...render( - <> - - - - } path={"/add-patient"} /> - Patients!

} /> - } /> -
-
-
- - - ), }); it("shows the form title", async () => { - renderWithUser(); + renderWithUserWithFacility(); expect( ( await screen.findAllByText(`Add new ${PATIENT_TERM}`, { @@ -295,7 +51,7 @@ describe("AddPatient", () => { describe("Choosing a country", () => { it("should show the state and zip code inputs for USA", async () => { - const { user } = renderWithUser(); + const { user } = renderWithUserWithFacility(); await user.selectOptions( screen.getByLabelText("Country", { exact: false }), @@ -305,7 +61,7 @@ describe("AddPatient", () => { expect(await screen.findByText("ZIP code")).toBeInTheDocument(); }); it("should show the state and zip code inputs for Canada", async () => { - const { user } = renderWithUser(); + const { user } = renderWithUserWithFacility(); await user.selectOptions( screen.getByLabelText("Country", { exact: false }), "CAN" @@ -314,7 +70,7 @@ describe("AddPatient", () => { expect(await screen.findByText("ZIP code")).toBeInTheDocument(); }); it("should show different states for Canada", async () => { - const { user } = renderWithUser(); + const { user } = renderWithUserWithFacility(); await user.selectOptions( screen.getByLabelText("Country", { exact: false }), @@ -329,8 +85,9 @@ describe("AddPatient", () => { await user.selectOptions(stateInput, "QC"); expect(stateInput.value).toBe("QC"); }); + it("should hide the state and zip code inputs for non-US countries", async () => { - const { user } = renderWithUser(); + const { user } = renderWithUserWithFacility(); await user.selectOptions( screen.getByLabelText("Country", { exact: false }), @@ -341,177 +98,9 @@ describe("AddPatient", () => { }); }); - describe("All required fields entered and submitting address verification", () => { - it("redirects to the person tab", async () => { - const { user } = renderWithUser(); - - await fillOutForm( - user, - { - "First Name": "Alice", - "Last Name": "Hamilton", - "Date of birth": "1970-09-22", - "Primary phone number": "617-432-1000", - "Email address": "foo@bar.org", - "Street address 1": "25 Shattuck St", - City: "Boston", - - "ZIP code": "02115", - }, - { Facility: mockFacilityID, State: "MA", Country: "USA" }, - { - "Phone type": { - label: "Mobile", - value: "MOBILE", - exact: true, - }, - "Would you like to receive your results via text message?": { - label: "Yes", - value: "SMS", - exact: false, - }, - Race: { - label: "Other", - value: "other", - exact: true, - }, - "Are you Hispanic or Latino?": { - label: "Prefer not to answer", - value: "refused", - exact: true, - }, - "Sex assigned at birth": { - label: "Female", - value: "female", - exact: true, - }, - "What's your gender identity?": { - label: "Female", - value: "female", - exact: true, - }, - } - ); - - await user.click(screen.queryAllByText(/Save Changes/i)[0]); - - await screen.findByText(/Address validation/i); - const modal = screen.getByRole("dialog"); - - await user.click( - within(modal).getByLabelText("Use address as entered", { - exact: false, - }) - ); - - await user.click( - within(modal).getByText("Save changes", { - exact: false, - }) - ); - expect(await screen.findByText("Patients!")).toBeInTheDocument(); - }); - it("surfaces an error if invalid zip code for state", async () => { - const { user } = renderWithUser(); - zipCodeSpy.mockReturnValue(false); - await fillOutForm( - user, - { - "First Name": "Alice", - "Last Name": "Hamilton", - "Date of birth": "1970-09-22", - "Primary phone number": "617-432-1000", - "Email address": "foo@bar.org", - "Street address 1": "25 Shattuck St", - City: "Boston", - "ZIP code": "02115", - }, - { Facility: mockFacilityID, State: "MA", Country: "USA" }, - { - "Phone type": { - label: "Mobile", - value: "MOBILE", - exact: true, - }, - "Would you like to receive your results via text message?": { - label: "Yes", - value: "SMS", - exact: false, - }, - Race: { - label: "Other", - value: "other", - exact: true, - }, - "Are you Hispanic or Latino?": { - label: "Prefer not to answer", - value: "refused", - exact: true, - }, - "Sex assigned at birth": { - label: "Female", - value: "female", - exact: true, - }, - } - ); - await user.click( - screen.queryAllByText("Save Changes", { - exact: false, - })[0] - ); - await screen.findByText(/Invalid ZIP code for this state/i); - }); - it("requires race field to be populated", async () => { - const { user } = renderWithUser(); - await fillOutForm( - user, - { - "First Name": "Alice", - "Last Name": "Hamilton", - "Date of birth": "1970-09-22", - "Primary phone number": "617-432-1000", - "Email address": "foo@bar.org", - "Street address 1": "25 Shattuck St", - City: "Boston", - - "ZIP code": "02115", - }, - { Facility: mockFacilityID, State: "MA", Country: "USA" }, - { - "Phone type": { - label: "Mobile", - value: "MOBILE", - exact: true, - }, - "Would you like to receive your results via text message?": { - label: "Yes", - value: "SMS", - exact: false, - }, - "Sex assigned at birth": { - label: "Female", - value: "female", - exact: true, - }, - } - ); - - await user.click( - screen.queryAllByText("Save Changes", { - exact: false, - })[0] - ); - - await waitFor(() => - expect(screen.queryByText("Error: Race is missing")) - ); - }); - }); - describe("facility select input", () => { it("is present in the form", () => { - renderWithUser(); + renderWithUserWithFacility(); const facilityInput = screen.getByLabelText("Facility", { exact: false, }) as HTMLSelectElement; @@ -519,7 +108,7 @@ describe("AddPatient", () => { }); it("defaults to no selection", () => { - renderWithUser(); + renderWithUserWithFacility(); const facilityInput = screen.getByLabelText("Facility", { exact: false, }) as HTMLSelectElement; @@ -527,7 +116,7 @@ describe("AddPatient", () => { }); it("updates its selection on change", async () => { - const { user } = renderWithUser(); + const { user } = renderWithUserWithFacility(); const facilityInput = screen.getByLabelText("Facility", { exact: false, }) as HTMLSelectElement; @@ -538,284 +127,10 @@ describe("AddPatient", () => { describe("With student ID", () => { it("allows student ID to be entered", async () => { - const { user } = renderWithUser(); + const { user } = renderWithUserWithFacility(); await user.selectOptions(screen.getByLabelText("Role"), "STUDENT"); expect(await screen.findByText("Student ID")).toBeInTheDocument(); }); }); - - describe("saving changes and starting a test", () => { - it("redirects to the queue after address validation", async () => { - const { user } = renderWithUser(); - await fillOutForm( - user, - { - "First Name": "Alice", - "Last Name": "Hamilton", - "Date of birth": "1970-09-22", - "Primary phone number": "617-432-1000", - "Email address": "foo@bar.org", - "Street address 1": "25 Shattuck St", - City: "Boston", - - "ZIP code": "02115", - }, - { Facility: mockFacilityID, State: "MA", Country: "USA" }, - { - "Phone type": { - label: "Mobile", - value: "MOBILE", - exact: true, - }, - "Would you like to receive your results via text message?": { - label: "Yes", - value: "SMS", - exact: false, - }, - Race: { - label: "Other", - value: "other", - exact: true, - }, - "Are you Hispanic or Latino?": { - label: "Prefer not to answer", - value: "refused", - exact: true, - }, - "Sex assigned at birth": { - label: "Female", - value: "female", - exact: true, - }, - "What's your gender identity?": { - label: "Female", - value: "female", - exact: true, - }, - } - ); - jest - .spyOn(smartyStreets, "suggestionIsCloseEnough") - .mockReturnValue(true); - - await user.click( - screen.queryAllByText("Save and start test", { - exact: false, - })[0] - ); - - //await new Promise((resolve) => setTimeout(resolve, 0)); - - expect( - screen.queryByText("Address validation", { - exact: false, - }) - ).not.toBeInTheDocument(); - - await screen.findByText(/Testing Queue!/i); - }); - - it("redirects to the queue with a patient id and selected facility id", async () => { - const { user } = renderWithUser(); - await fillOutForm( - user, - { - "First Name": "Alice", - "Last Name": "Hamilton", - "Date of birth": "1970-09-22", - "Primary phone number": "617-432-1000", - "Email address": "foo@bar.org", - "Street address 1": "25 Shattuck St", - City: "Boston", - "ZIP code": "02115", - }, - { Facility: mockFacilityID, State: "MA", Country: "USA" }, - { - "Phone type": { - label: "Mobile", - value: "MOBILE", - exact: true, - }, - "Would you like to receive your results via text message?": { - label: "Yes", - value: "SMS", - exact: false, - }, - Race: { - label: "Other", - value: "other", - exact: true, - }, - "Are you Hispanic or Latino?": { - label: "Prefer not to answer", - value: "refused", - exact: true, - }, - "Sex assigned at birth": { - label: "Female", - value: "female", - exact: true, - }, - "What's your gender identity?": { - label: "Female", - value: "female", - exact: true, - }, - } - ); - await user.click( - screen.queryAllByText("Save and start test", { - exact: false, - })[0] - ); - expect( - await screen.findByText("Address validation", { - exact: false, - }) - ).toBeInTheDocument(); - - const modal = screen.getByRole("dialog"); - - await user.click( - within(modal).getByLabelText("Use address as entered", { - exact: false, - }) - ); - await user.click( - within(modal).getByText("Save changes", { - exact: false, - }) - ); - - expect( - await screen.findByText("Testing Queue!", { exact: false }) - ).toBeInTheDocument(); - - expect( - await screen.findByText("facility-id-001", { exact: false }) - ).toBeInTheDocument(); - - expect( - screen.getByText("153f661f-b6ea-4711-b9ab-487b95198cce", { - exact: false, - }) - ).toBeInTheDocument(); - }); - }); - }); - - describe("when attempting to create an existing patient ", () => { - const renderWithUser = (mocks: any[]) => ({ - user: userEvent.setup(), - ...render( - - - - } path={"/add-patient/"} /> - Patients!

} /> -
-
-
- ), - }); - it("does not open modal if no patient with matching data exists", async () => { - let patientExistsMock = jest.fn(); - const mocks = [ - { - request: { - query: PATIENT_EXISTS, - variables: { - firstName: "Alice", - lastName: "Hamilton", - birthDate: "1970-09-22", - facilityId: mockFacilityID, - }, - }, - result: () => { - patientExistsMock(); - return { - data: { - patientExistsWithoutZip: false, - }, - }; - }, - }, - ]; - - const { user } = renderWithUser(mocks); - - await fillOutForm( - user, - { - "First Name": "Alice", - "Last Name": "Hamilton", - }, - { Facility: mockFacilityID }, - {} - ); - - // The duplicate patient check is triggered on-blur from one of the identifying data fields - await user.type( - screen.getByLabelText("Date of birth", { exact: false }), - "1970-09-22" - ); - await user.tab(); - - await waitFor(() => { - expect(patientExistsMock).toHaveBeenCalledTimes(1); - }); - - expect( - screen.queryByText("This patient is already registered", { - exact: false, - }) - ).not.toBeInTheDocument(); - }); - - it("displays modal when all identifying data fields have been entered with an existing patient's data", async () => { - const mocks = [ - { - request: { - query: PATIENT_EXISTS, - variables: { - firstName: "Alice", - lastName: "Hamilton", - birthDate: "1970-09-22", - facilityId: mockFacilityID, - }, - }, - result: { - data: { - patientExistsWithoutZip: true, - }, - }, - }, - ]; - - const { user } = renderWithUser(mocks); - - await fillOutForm( - user, - { - "First Name": "Alice", - "Last Name": "Hamilton", - }, - { Facility: mockFacilityID }, - {} - ); - - // The duplicate patient check is triggered on-blur from one of the identifying data fields - await user.type( - screen.getByLabelText("Date of birth", { exact: false }), - "1970-09-22" - ); - await user.tab(); - - expect( - await screen.findByText("This patient is already registered", { - exact: false, - }) - ).toBeInTheDocument(); - }); }); }); diff --git a/frontend/src/app/patients/AddPatient.tsx b/frontend/src/app/patients/AddPatient.tsx index c99ca44a85e..143f46760fd 100644 --- a/frontend/src/app/patients/AddPatient.tsx +++ b/frontend/src/app/patients/AddPatient.tsx @@ -53,6 +53,7 @@ export const EMPTY_PERSON: Nullable = { country: "USA", preferredLanguage: null, testResultDelivery: null, + notes: null, }; export const PATIENT_EXISTS = gql` @@ -98,6 +99,7 @@ export const ADD_PATIENT = gql` $employedInHealthcare: Boolean $preferredLanguage: String $testResultDelivery: TestResultDeliveryPreference + $notes: String ) { addPatient( facilityId: $facilityId @@ -125,6 +127,7 @@ export const ADD_PATIENT = gql` employedInHealthcare: $employedInHealthcare preferredLanguage: $preferredLanguage testResultDelivery: $testResultDelivery + notes: $notes ) { internalId facility { diff --git a/frontend/src/app/patients/AddPatientAddressVerification.test.tsx b/frontend/src/app/patients/AddPatientAddressVerification.test.tsx new file mode 100644 index 00000000000..74166cd8351 --- /dev/null +++ b/frontend/src/app/patients/AddPatientAddressVerification.test.tsx @@ -0,0 +1,188 @@ +import * as router from "react-router"; +import { screen, waitFor, within } from "@testing-library/react"; + +import * as smartyStreets from "../utils/smartyStreets"; + +import { + fillOutForm, + mockFacilityID, + renderWithUserWithFacility, +} from "./AddPatientTestUtils"; + +// These tests have been broken down into multiple files so they can execute in parallel +describe("Add Patient: All required fields entered and submitting address verification", () => { + beforeEach(async () => { + jest.spyOn(smartyStreets, "getBestSuggestion").mockImplementation(); + jest.spyOn(smartyStreets, "suggestionIsCloseEnough").mockReturnValue(false); + jest.spyOn(smartyStreets, "getZipCodeData").mockResolvedValue(undefined); + }); + + it("redirects to the person tab", async () => { + const mockNavigate = jest.fn(); + jest.spyOn(router, "useNavigate").mockImplementation(() => mockNavigate); + const { user } = renderWithUserWithFacility(); + + await fillOutForm( + { + "First Name": "Alice", + "Last Name": "Hamilton", + "Date of birth": "1970-09-22", + "Primary phone number": "617-432-1000", + "Email address": "foo@bar.org", + "Street address 1": "25 Shattuck St", + City: "Boston", + "ZIP code": "02115", + Notes: "Green tent", + }, + { Facility: mockFacilityID, State: "MA", Country: "USA" }, + { + "Phone type": { + label: "Mobile", + value: "MOBILE", + exact: true, + }, + "Would you like to receive your results via text message?": { + label: "Yes", + value: "SMS", + exact: false, + }, + Race: { + label: "Other", + value: "other", + exact: true, + }, + "Are you Hispanic or Latino?": { + label: "Prefer not to answer", + value: "refused", + exact: true, + }, + "Sex assigned at birth": { + label: "Female", + value: "female", + exact: true, + }, + "What's your gender identity?": { + label: "Female", + value: "female", + exact: true, + }, + } + ); + + await user.click(screen.queryAllByText(/Save Changes/i)[0]); + await screen.findByRole("heading", { name: "Address validation" }); + const modal = screen.getByRole("dialog"); + + await user.click( + within(modal).getByLabelText("Use address as entered", { + exact: false, + }) + ); + await user.click( + within(modal).getByRole("button", { name: "Save changes" }) + ); + await waitFor(() => + expect(mockNavigate).toHaveBeenCalledWith( + "/patients/?facility=b0d2041f-93c9-4192-b19a-dd99c0044a7e", + {} + ) + ); + }); + + it("surfaces an error if invalid zip code for state", async () => { + const { user } = renderWithUserWithFacility(); + let zipCodeSpy: jest.SpyInstance; + zipCodeSpy = jest + .spyOn(smartyStreets, "isValidZipCodeForState") + .mockReturnValue(true); + zipCodeSpy.mockReturnValue(false); + await fillOutForm( + { + "First Name": "Alice", + "Last Name": "Hamilton", + "Date of birth": "1970-09-22", + "Primary phone number": "617-432-1000", + "Email address": "foo@bar.org", + "Street address 1": "25 Shattuck St", + City: "Boston", + "ZIP code": "02115", + }, + { Facility: mockFacilityID, State: "MA", Country: "USA" }, + { + "Phone type": { + label: "Mobile", + value: "MOBILE", + exact: true, + }, + "Would you like to receive your results via text message?": { + label: "Yes", + value: "SMS", + exact: false, + }, + Race: { + label: "Other", + value: "other", + exact: true, + }, + "Are you Hispanic or Latino?": { + label: "Prefer not to answer", + value: "refused", + exact: true, + }, + "Sex assigned at birth": { + label: "Female", + value: "female", + exact: true, + }, + } + ); + await user.click( + screen.queryAllByText("Save Changes", { + exact: false, + })[0] + ); + await screen.findByText(/Invalid ZIP code for this state/i); + }); + + it("requires race field to be populated", async () => { + const { user } = renderWithUserWithFacility(); + await fillOutForm( + { + "First Name": "Alice", + "Last Name": "Hamilton", + "Date of birth": "1970-09-22", + "Primary phone number": "617-432-1000", + "Email address": "foo@bar.org", + "Street address 1": "25 Shattuck St", + City: "Boston", + "ZIP code": "02115", + }, + { Facility: mockFacilityID, State: "MA", Country: "USA" }, + { + "Phone type": { + label: "Mobile", + value: "MOBILE", + exact: true, + }, + "Would you like to receive your results via text message?": { + label: "Yes", + value: "SMS", + exact: false, + }, + "Sex assigned at birth": { + label: "Female", + value: "female", + exact: true, + }, + } + ); + + await user.click( + screen.queryAllByText("Save Changes", { + exact: false, + })[0] + ); + + await waitFor(() => expect(screen.queryByText("Error: Race is missing"))); + }); +}); diff --git a/frontend/src/app/patients/AddPatientExistinPatient.test.tsx b/frontend/src/app/patients/AddPatientExistinPatient.test.tsx new file mode 100644 index 00000000000..562d2fb8619 --- /dev/null +++ b/frontend/src/app/patients/AddPatientExistinPatient.test.tsx @@ -0,0 +1,123 @@ +import { render, screen, waitFor } from "@testing-library/react"; +import { Provider } from "react-redux"; +import { MockedProvider } from "@apollo/client/testing"; +import { Route } from "react-router-dom"; +import userEvent from "@testing-library/user-event"; + +import AddPatient, { PATIENT_EXISTS } from "./AddPatient"; +import { + fillOutForm, + mockFacilityID, + store, + RouterWithFacility, +} from "./AddPatientTestUtils"; + +// These tests have been broken down into multiple files so they can execute in parallel +describe("Add Patient: when attempting to create an existing patient ", () => { + const renderWithUser = (mocks: any[]) => ({ + user: userEvent.setup(), + ...render( + + + + } path={"/add-patient/"} /> + Patients!

} /> +
+
+
+ ), + }); + it("does not open modal if no patient with matching data exists", async () => { + let patientExistsMock = jest.fn(); + const mocks = [ + { + request: { + query: PATIENT_EXISTS, + variables: { + firstName: "Alice", + lastName: "Hamilton", + birthDate: "1970-09-22", + facilityId: mockFacilityID, + }, + }, + result: () => { + patientExistsMock(); + return { + data: { + patientExistsWithoutZip: false, + }, + }; + }, + }, + ]; + + const { user } = renderWithUser(mocks); + + await fillOutForm( + { + "First Name": "Alice", + "Last Name": "Hamilton", + }, + { Facility: mockFacilityID }, + {} + ); + + // The duplicate patient check is triggered on-blur from one of the identifying data fields + await user.click(screen.getByLabelText("Date of birth", { exact: false })); + await user.paste("1970-09-22"); + await user.tab(); + + await waitFor(() => { + expect(patientExistsMock).toHaveBeenCalledTimes(1); + }); + + expect( + screen.queryByText("This patient is already registered", { + exact: false, + }) + ).not.toBeInTheDocument(); + }); + + it("displays modal when all identifying data fields have been entered with an existing patient's data", async () => { + const mocks = [ + { + request: { + query: PATIENT_EXISTS, + variables: { + firstName: "Alice", + lastName: "Hamilton", + birthDate: "1970-09-22", + facilityId: mockFacilityID, + }, + }, + result: { + data: { + patientExistsWithoutZip: true, + }, + }, + }, + ]; + + const { user } = renderWithUser(mocks); + + await fillOutForm( + { + "First Name": "Alice", + "Last Name": "Hamilton", + }, + { Facility: mockFacilityID }, + {} + ); + + // The duplicate patient check is triggered on-blur from one of the identifying data fields + await user.click(screen.getByLabelText("Date of birth", { exact: false })); + await user.paste("1970-09-22"); + await user.tab(); + + expect( + await screen.findByText("This patient is already registered", { + exact: false, + }) + ).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/app/patients/AddPatientStartsTest.test.tsx b/frontend/src/app/patients/AddPatientStartsTest.test.tsx new file mode 100644 index 00000000000..3d27cdef74c --- /dev/null +++ b/frontend/src/app/patients/AddPatientStartsTest.test.tsx @@ -0,0 +1,178 @@ +import { screen, waitFor, within } from "@testing-library/react"; +import * as router from "react-router"; + +import * as smartyStreets from "../utils/smartyStreets"; + +import { + fillOutForm, + mockFacilityID, + renderWithUserWithFacility, +} from "./AddPatientTestUtils"; + +describe("Add Patient: saving changes and starting a test", () => { + const mockNavigate = jest.fn(); + beforeEach(async () => { + jest.spyOn(smartyStreets, "getBestSuggestion").mockImplementation(); + jest.spyOn(smartyStreets, "suggestionIsCloseEnough").mockReturnValue(false); + jest.spyOn(smartyStreets, "getZipCodeData").mockResolvedValue(undefined); + + jest.spyOn(router, "useNavigate").mockImplementation(() => mockNavigate); + }); + + it("redirects to the queue after address validation", async () => { + jest.spyOn(smartyStreets, "suggestionIsCloseEnough").mockReturnValue(true); + const { user } = renderWithUserWithFacility(); + await fillOutForm( + { + "First Name": "Alice", + "Last Name": "Hamilton", + "Date of birth": "1970-09-22", + "Primary phone number": "617-432-1000", + "Email address": "foo@bar.org", + "Street address 1": "25 Shattuck St", + City: "Boston", + "ZIP code": "02115", + notes: "Green tent", + }, + { Facility: mockFacilityID, State: "MA", Country: "USA" }, + { + "Phone type": { + label: "Mobile", + value: "MOBILE", + exact: true, + }, + "Would you like to receive your results via text message?": { + label: "Yes", + value: "SMS", + exact: false, + }, + Race: { + label: "Other", + value: "other", + exact: true, + }, + "Are you Hispanic or Latino?": { + label: "Prefer not to answer", + value: "refused", + exact: true, + }, + "Sex assigned at birth": { + label: "Female", + value: "female", + exact: true, + }, + "What's your gender identity?": { + label: "Female", + value: "female", + exact: true, + }, + } + ); + + await user.click( + screen.queryAllByText("Save and start test", { + exact: false, + })[0] + ); + + expect( + screen.queryByText("Address validation", { + exact: false, + }) + ).not.toBeInTheDocument(); + + await waitFor(() => + expect(mockNavigate).toHaveBeenCalledWith( + "/queue?facility=facility-id-001", + { + state: { + patientId: "153f661f-b6ea-4711-b9ab-487b95198cce", + }, + } + ) + ); + }); + + it("redirects to the queue with a patient id and selected facility id", async () => { + const { user } = renderWithUserWithFacility(); + await fillOutForm( + { + "First Name": "Alice", + "Last Name": "Hamilton", + "Date of birth": "1970-09-22", + "Primary phone number": "617-432-1000", + "Email address": "foo@bar.org", + "Street address 1": "25 Shattuck St", + City: "Boston", + "ZIP code": "02115", + notes: "Green tent", + }, + { Facility: mockFacilityID, State: "MA", Country: "USA" }, + { + "Phone type": { + label: "Mobile", + value: "MOBILE", + exact: true, + }, + "Would you like to receive your results via text message?": { + label: "Yes", + value: "SMS", + exact: false, + }, + Race: { + label: "Other", + value: "other", + exact: true, + }, + "Are you Hispanic or Latino?": { + label: "Prefer not to answer", + value: "refused", + exact: true, + }, + "Sex assigned at birth": { + label: "Female", + value: "female", + exact: true, + }, + "What's your gender identity?": { + label: "Female", + value: "female", + exact: true, + }, + } + ); + await user.click( + screen.queryAllByText("Save and start test", { + exact: false, + })[0] + ); + expect( + await screen.findByText("Address validation", { + exact: false, + }) + ).toBeInTheDocument(); + + const modal = screen.getByRole("dialog"); + + await user.click( + within(modal).getByLabelText("Use address as entered", { + exact: false, + }) + ); + await user.click( + within(modal).getByText("Save changes", { + exact: false, + }) + ); + await waitFor(() => + expect(mockNavigate).toHaveBeenCalledWith( + "/queue?facility=facility-id-001", + { + state: { + patientId: "153f661f-b6ea-4711-b9ab-487b95198cce", + }, + } + ) + ); + }); +}); diff --git a/frontend/src/app/patients/AddPatientTestUtils.tsx b/frontend/src/app/patients/AddPatientTestUtils.tsx new file mode 100644 index 00000000000..45d786ad619 --- /dev/null +++ b/frontend/src/app/patients/AddPatientTestUtils.tsx @@ -0,0 +1,247 @@ +import configureStore from "redux-mock-store"; +import { MemoryRouter, Route, Routes, useLocation } from "react-router-dom"; +import { fireEvent, render, screen, within } from "@testing-library/react"; +import { Provider } from "react-redux"; +import { MockedProvider } from "@apollo/client/testing"; +import userEvent from "@testing-library/user-event"; + +import SRToastContainer from "../commonComponents/SRToastContainer"; + +import AddPatient, { ADD_PATIENT, PATIENT_EXISTS } from "./AddPatient"; + +export const mockFacilityID = "b0d2041f-93c9-4192-b19a-dd99c0044a7e"; +export const mockStore = configureStore([]); +export const store = mockStore({ + facilities: [{ id: mockFacilityID, name: "123" }], + organization: { name: "Test Organization" }, +}); + +export const fillOutForm = async ( + inputs: { [label: string]: string }, + dropdowns: { [label: string]: string }, + inputGroups: { + [legend: string]: { label: string; value: string; exact?: boolean }; + } +) => { + const inputElements = Object.entries(inputs); + + for (const [label, value] of inputElements) { + fireEvent.change( + screen.getByLabelText(label, { + exact: false, + }), + { target: { value } } + ); + } + + const dropDownElements = Object.entries(dropdowns); + + for (const [label, value] of dropDownElements) { + fireEvent.change( + screen.getByLabelText(label, { + exact: false, + }), + { target: { value } } + ); + } + + const inputGroupsElements = Object.entries(inputGroups); + + for (const [legend, { label, exact }] of inputGroupsElements) { + const fieldset = screen + .getByText(legend, { + exact: true, + }) + .closest("fieldset"); + if (fieldset === null) { + throw Error(`Unable to corresponding fieldset for ${legend}`); + } + fireEvent.click( + within(fieldset).getByLabelText(label, { + exact: exact ?? false, + }) + ); + } +}; + +export const renderWithUserNoFacility = () => ({ + user: userEvent.setup(), + ...render( + + + + + } /> + + + + + ), +}); + +const addPatientRequestParams = { + firstName: "Alice", + middleName: null, + lastName: "Hamilton", + lookupId: null, + birthDate: "1970-09-22", + street: "25 Shattuck St", + streetTwo: null, + city: "Boston", + state: "MA", + zipCode: "02115", + notes: "Green tent", + country: "USA", + telephone: null, + phoneNumbers: [ + { + type: "MOBILE", + number: "617-432-1000", + }, + ], + role: null, + emails: ["foo@bar.org"], + county: "", + race: "other", + ethnicity: "refused", + gender: "female", + genderIdentity: "female", + facilityId: mockFacilityID, + preferredLanguage: null, + testResultDelivery: "SMS", +}; + +const addPatientRequestNoDelivery = { + ...addPatientRequestParams, + testResultDelivery: null, +}; + +const addPatientRequestNoAddressValidation = { + ...addPatientRequestParams, + county: null, +}; + +const mocks = [ + { + request: { + query: PATIENT_EXISTS, + variables: { + firstName: "Alice", + lastName: "Hamilton", + birthDate: "1970-09-22", + facilityId: "b0d2041f-93c9-4192-b19a-dd99c0044a7e", + }, + }, + result: { + data: { + patientExistsWithoutZip: false, + }, + }, + }, + { + request: { + query: PATIENT_EXISTS, + variables: { + firstName: "Alice", + lastName: "Hamilton", + birthDate: "1970-09-22", + facilityId: "", + }, + }, + result: { + data: { + patientExistsWithoutZip: false, + }, + }, + }, + { + request: { + query: ADD_PATIENT, + variables: addPatientRequestParams, + }, + result: { + data: { + addPatient: { + internalId: "153f661f-b6ea-4711-b9ab-487b95198cce", + facility: { + id: "facility-id-001", + }, + }, + }, + }, + }, + { + request: { + query: ADD_PATIENT, + variables: addPatientRequestNoDelivery, + }, + result: { + data: { + addPatient: { + internalId: "153f661f-b6ea-4711-b9ab-487b95198cce", + facility: { + id: "facility-id-001", + }, + }, + }, + }, + }, + { + request: { + query: ADD_PATIENT, + variables: addPatientRequestNoAddressValidation, + }, + result: { + data: { + addPatient: { + internalId: "153f661f-b6ea-4711-b9ab-487b95198cce", + facility: { + id: "facility-id-001", + }, + }, + }, + }, + }, +]; + +type LocationOptions = { + search: string; + pathname: string; + state: { + patientId: string; + }; +}; +const Queue = () => { + const location = useLocation() as LocationOptions; + + return ( +

+ Testing Queue! {location.search} {location.state.patientId} +

+ ); +}; +export const renderWithUserWithFacility = () => ({ + user: userEvent.setup(), + ...render( + <> + + + + } path={"/add-patient"} /> + Patients!

} /> + } /> +
+
+
+ + + ), +}); + +export const RouterWithFacility: React.FC = ({ + children, +}) => ( + + {children} + +); diff --git a/frontend/src/app/patients/Components/ManageEmails.test.tsx b/frontend/src/app/patients/Components/ManageEmails.test.tsx index b277125d42c..b631fbd6bbd 100644 --- a/frontend/src/app/patients/Components/ManageEmails.test.tsx +++ b/frontend/src/app/patients/Components/ManageEmails.test.tsx @@ -41,6 +41,7 @@ const patient = { testResultDelivery: null, preferredLanguage: null, tribalAffiliation: null, + notes: null, }; function ManageEmailsContainer() { diff --git a/frontend/src/app/patients/Components/PersonForm.tsx b/frontend/src/app/patients/Components/PersonForm.tsx index d330ba807e9..ce156255f94 100644 --- a/frontend/src/app/patients/Components/PersonForm.tsx +++ b/frontend/src/app/patients/Components/PersonForm.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useState, useEffect, useRef } from "react"; import { SchemaOf } from "yup"; import { useTranslation } from "react-i18next"; -import { ComboBox } from "@trussworks/react-uswds"; +import { ComboBox, Label, Textarea } from "@trussworks/react-uswds"; import { canadianProvinceCodes, @@ -611,6 +611,26 @@ const PersonForm = (props: Props) => { ) : null} +
+
+ + + {t("patient.form.notes.helpText")} + +