diff --git a/.github/workflows/main-deploy.yaml b/.github/workflows/main-deploy.yaml index c47948e3a..2e86b6382 100644 --- a/.github/workflows/main-deploy.yaml +++ b/.github/workflows/main-deploy.yaml @@ -22,11 +22,21 @@ jobs: keycloak-client: ${{ secrets.KEYCLOAK_APPOINTMENTS_FRONTEND_CLIENT }} keycloak-realm: ${{ secrets.KEYCLOAK_REALM }} + queue-management-frontend-cypress: + name: Queue Management Frontend Cypress + uses: ./.github/workflows/reusable-queue-management-frontend-cypress.yaml + secrets: + cypress-project-id: ${{ secrets.CYPRESS_PROJECT_ID }} + cypress-record-key: ${{ secrets.CYPRESS_RECORD_KEY }} + keycloak-auth-url: ${{ secrets.KEYCLOAK_AUTH_URL_DEV }}/auth/ + keycloak-client: ${{ secrets.KEYCLOAK_APPOINTMENTS_FRONTEND_CLIENT }} + keycloak-realm: ${{ secrets.KEYCLOAK_REALM }} + ##### BUILD ################################################################## appointment-frontend: name: appointment-frontend - needs: appointment-frontend-cypress + needs: [appointment-frontend-cypress, queue-management-frontend-cypress] uses: ./.github/workflows/reusable-build-dockerfile.yaml secrets: artifactory-password: ${{ secrets.ARTIFACTORY_PASSWORD }} @@ -48,7 +58,7 @@ jobs: feedback-api: name: feedback-api - needs: appointment-frontend-cypress + needs: [appointment-frontend-cypress, queue-management-frontend-cypress] uses: ./.github/workflows/reusable-build-s2i.yaml secrets: namespace-theq: ${{ secrets.LICENCE_PLATE_THEQ }}-tools @@ -67,7 +77,7 @@ jobs: notifications-api: name: notifications-api - needs: appointment-frontend-cypress + needs: [appointment-frontend-cypress, queue-management-frontend-cypress] uses: ./.github/workflows/reusable-build-s2i.yaml secrets: namespace-theq: ${{ secrets.LICENCE_PLATE_THEQ }}-tools @@ -86,7 +96,7 @@ jobs: queue-management-api: name: queue-management-api - needs: appointment-frontend-cypress + needs: [appointment-frontend-cypress, queue-management-frontend-cypress] uses: ./.github/workflows/reusable-build-s2i.yaml secrets: artifactory-password: ${{ secrets.ARTIFACTORY_PASSWORD }} @@ -108,7 +118,7 @@ jobs: queue-management-frontend: name: queue-management-frontend - needs: appointment-frontend-cypress + needs: [appointment-frontend-cypress, queue-management-frontend-cypress] uses: ./.github/workflows/reusable-build-dockerfile.yaml secrets: artifactory-password: ${{ secrets.ARTIFACTORY_PASSWORD }} @@ -130,7 +140,7 @@ jobs: send-appointment-reminder-crond: name: send-appointment-reminder-crond - needs: appointment-frontend-cypress + needs: [appointment-frontend-cypress, queue-management-frontend-cypress] uses: ./.github/workflows/reusable-build-dockerfile.yaml secrets: artifactory-password: ${{ secrets.ARTIFACTORY_PASSWORD }} diff --git a/.github/workflows/prautorun.yaml b/.github/workflows/prautorun.yaml new file mode 100644 index 000000000..a33cf81dd --- /dev/null +++ b/.github/workflows/prautorun.yaml @@ -0,0 +1,343 @@ +name: Pull Request Deploy Auto Run +on: + pull_request: + workflow_dispatch: + inputs: + pr-number: + description: "Pull Request Number:" + type: string + required: true + namespace: + description: "Deploy To:" + type: choice + required: true + options: + - The Q Dev + - QMS Dev + - The Q Test + +jobs: + + ##### SETUP ################################################################## + + parse-inputs: + name: refs/pull/${{ github.event.pull_request.number }}/head to echo ${{ github.event.inputs.namespace }} + runs-on: ubuntu-latest + outputs: + environment: ${{ steps.parse.outputs.environment }} + image-tag: ${{ steps.parse.outputs.image-tag }} + push-qms: ${{ steps.parse.outputs.push-qms }} + push-theq: ${{ steps.parse.outputs.push-theq }} + ref: ${{ steps.parse.outputs.ref }} + + steps: + # Use the input values to create more coding-friendly values. + - name: Parse Inputs + id: parse + run: | + ENVIRONMENT="dev" + echo ENVIRONMENT:$ENVIRONMENT + echo "environment=$ENVIRONMENT" >> $GITHUB_OUTPUT + + IMAGE_TAG=pr${{ github.event.pull_request.number }} + echo IMAGE_TAG:$IMAGE_TAG + echo "image-tag=$IMAGE_TAG" >> $GITHUB_OUTPUT + + if [ $GITHUB_REPOSITORY_OWNER != "bcgov" ]; then + # Never push in forks - useful and safer for development. + PUSH_QMS=false + PUSH_THEQ=false + elif [[ "${{ github.event.inputs.namespace }}" == QMS* ]]; then + PUSH_QMS=true + PUSH_THEQ=false + else + PUSH_QMS=false + PUSH_THEQ=true + fi + + echo PUSH_QMS:$PUSH_QMS + echo "push-qms=$PUSH_QMS" >> $GITHUB_OUTPUT + + echo PUSH_THEQ:$PUSH_THEQ + echo "push-theq=$PUSH_THEQ" >> $GITHUB_OUTPUT + + REF=refs/pull/${{ github.event.pull_request.number }}/head + echo REF:$REF + echo "ref=$REF" >> $GITHUB_OUTPUT + + + ##### TEST ################################################################### + + appointment-frontend-cypress: + name: Appointment Frontend Cypress + needs: parse-inputs + uses: ./.github/workflows/tyu-reusable-appointment-frontend-cypress.yaml + secrets: + bceid-endpoint: ${{ secrets.CYPRESS_BCEID_ENDPOINT }} + bceid-password: ${{ secrets.CYPRESS_BCEID_PASSWORD }} + bceid-username: ${{ secrets.CYPRESS_BCEID_USERNAME }} + cypress-project-id: ${{ secrets.CYPRESS_PROJECT_ID }} + cypress-record-key: ${{ secrets.CYPRESS_RECORD_KEY }} + keycloak-auth-url: ${{ secrets.KEYCLOAK_AUTH_URL_DEV }}/auth/ + keycloak-client: ${{ secrets.KEYCLOAK_APPOINTMENTS_FRONTEND_CLIENT }} + keycloak-realm: ${{ secrets.KEYCLOAK_REALM }} + with: + ref: ${{ needs.parse-inputs.outputs.ref }} + + ##### BUILD ################################################################## + + appointment-frontend: + name: appointment-frontend + needs: [parse-inputs, appointment-frontend-cypress] + uses: ./.github/workflows/reusable-build-dockerfile.yaml + secrets: + artifactory-password: ${{ secrets.ARTIFACTORY_PASSWORD }} + artifactory-registry: ${{ secrets.ARTIFACTORY_REGISTRY }} + artifactory-username: ${{ secrets.ARTIFACTORY_USERNAME }} + namespace-theq: ${{ secrets.LICENCE_PLATE_THEQ }}-tools + namespace-theq-password: ${{ secrets.SA_PASSWORD_THEQ_TOOLS }} + namespace-theq-username: ${{ secrets.SA_USERNAME }} + namespace-qms: ${{ secrets.LICENCE_PLATE_QMS }}-tools + namespace-qms-password: ${{ secrets.SA_PASSWORD_QMS_TOOLS }} + namespace-qms-username: ${{ secrets.SA_USERNAME }} + openshift-registry: ${{ secrets.OPENSHIFT_REGISTRY }} + with: + ref: ${{ needs.parse-inputs.outputs.ref }} + directory: appointment-frontend + image-name: appointment-nginx-frontend + image-tags: ${{ needs.parse-inputs.outputs.image-tag }} + push-qms: ${{ needs.parse-inputs.outputs.push-qms == 'true' }} + push-theq: ${{ needs.parse-inputs.outputs.push-theq == 'true' }} + + feedback-api: + name: feedback-api + needs: [parse-inputs, appointment-frontend-cypress] + uses: ./.github/workflows/reusable-build-s2i.yaml + secrets: + namespace-theq: ${{ secrets.LICENCE_PLATE_THEQ }}-tools + namespace-theq-password: ${{ secrets.SA_PASSWORD_THEQ_TOOLS }} + namespace-theq-username: ${{ secrets.SA_USERNAME }} + namespace-qms: ${{ secrets.LICENCE_PLATE_QMS }}-tools + namespace-qms-password: ${{ secrets.SA_PASSWORD_QMS_TOOLS }} + namespace-qms-username: ${{ secrets.SA_USERNAME }} + openshift-registry: ${{ secrets.OPENSHIFT_REGISTRY }} + with: + ref: ${{ needs.parse-inputs.outputs.ref }} + directory: feedback-api + image-name: feedback-api + image-tags: ${{ needs.parse-inputs.outputs.image-tag }} + push-qms: ${{ needs.parse-inputs.outputs.push-qms == 'true' }} + push-theq: ${{ needs.parse-inputs.outputs.push-theq == 'true' }} + + notifications-api: + name: notifications-api + needs: [parse-inputs, appointment-frontend-cypress] + uses: ./.github/workflows/reusable-build-s2i.yaml + secrets: + namespace-theq: ${{ secrets.LICENCE_PLATE_THEQ }}-tools + namespace-theq-password: ${{ secrets.SA_PASSWORD_THEQ_TOOLS }} + namespace-theq-username: ${{ secrets.SA_USERNAME }} + namespace-qms: ${{ secrets.LICENCE_PLATE_QMS }}-tools + namespace-qms-password: ${{ secrets.SA_PASSWORD_QMS_TOOLS }} + namespace-qms-username: ${{ secrets.SA_USERNAME }} + openshift-registry: ${{ secrets.OPENSHIFT_REGISTRY }} + with: + ref: ${{ needs.parse-inputs.outputs.ref }} + directory: notifications-api + image-name: notifications-api + image-tags: ${{ needs.parse-inputs.outputs.image-tag }} + push-qms: ${{ needs.parse-inputs.outputs.push-qms == 'true' }} + push-theq: ${{ needs.parse-inputs.outputs.push-theq == 'true' }} + + queue-management-api: + name: queue-management-api + needs: [parse-inputs, appointment-frontend-cypress] + uses: ./.github/workflows/reusable-build-s2i.yaml + secrets: + artifactory-password: ${{ secrets.ARTIFACTORY_PASSWORD }} + artifactory-registry: ${{ secrets.ARTIFACTORY_REGISTRY }} + artifactory-username: ${{ secrets.ARTIFACTORY_USERNAME }} + namespace-theq: ${{ secrets.LICENCE_PLATE_THEQ }}-tools + namespace-theq-password: ${{ secrets.SA_PASSWORD_THEQ_TOOLS }} + namespace-theq-username: ${{ secrets.SA_USERNAME }} + namespace-qms: ${{ secrets.LICENCE_PLATE_QMS }}-tools + namespace-qms-password: ${{ secrets.SA_PASSWORD_QMS_TOOLS }} + namespace-qms-username: ${{ secrets.SA_USERNAME }} + openshift-registry: ${{ secrets.OPENSHIFT_REGISTRY }} + with: + ref: ${{ needs.parse-inputs.outputs.ref }} + directory: api + image-name: queue-management-api + image-tags: ${{ needs.parse-inputs.outputs.image-tag }} + push-qms: ${{ needs.parse-inputs.outputs.push-qms == 'true' }} + push-theq: ${{ needs.parse-inputs.outputs.push-theq == 'true' }} + + queue-management-frontend: + name: queue-management-frontend + needs: [parse-inputs, appointment-frontend-cypress] + uses: ./.github/workflows/reusable-build-dockerfile.yaml + secrets: + artifactory-password: ${{ secrets.ARTIFACTORY_PASSWORD }} + artifactory-registry: ${{ secrets.ARTIFACTORY_REGISTRY }} + artifactory-username: ${{ secrets.ARTIFACTORY_USERNAME }} + namespace-theq: ${{ secrets.LICENCE_PLATE_THEQ }}-tools + namespace-theq-password: ${{ secrets.SA_PASSWORD_THEQ_TOOLS }} + namespace-theq-username: ${{ secrets.SA_USERNAME }} + namespace-qms: ${{ secrets.LICENCE_PLATE_QMS }}-tools + namespace-qms-password: ${{ secrets.SA_PASSWORD_QMS_TOOLS }} + namespace-qms-username: ${{ secrets.SA_USERNAME }} + openshift-registry: ${{ secrets.OPENSHIFT_REGISTRY }} + with: + ref: ${{ needs.parse-inputs.outputs.ref }} + directory: frontend + image-name: queue-management-nginx-frontend + image-tags: ${{ needs.parse-inputs.outputs.image-tag }} + push-qms: ${{ needs.parse-inputs.outputs.push-qms == 'true' }} + push-theq: ${{ needs.parse-inputs.outputs.push-theq == 'true' }} + + send-appointment-reminder-crond: + name: send-appointment-reminder-crond + needs: [parse-inputs, appointment-frontend-cypress] + uses: ./.github/workflows/reusable-build-dockerfile.yaml + secrets: + artifactory-password: ${{ secrets.ARTIFACTORY_PASSWORD }} + artifactory-registry: ${{ secrets.ARTIFACTORY_REGISTRY }} + artifactory-username: ${{ secrets.ARTIFACTORY_USERNAME }} + namespace-theq: ${{ secrets.LICENCE_PLATE_THEQ }}-tools + namespace-theq-password: ${{ secrets.SA_PASSWORD_THEQ_TOOLS }} + namespace-theq-username: ${{ secrets.SA_USERNAME }} + namespace-qms: ${{ secrets.LICENCE_PLATE_QMS }}-tools + namespace-qms-password: ${{ secrets.SA_PASSWORD_QMS_TOOLS }} + namespace-qms-username: ${{ secrets.SA_USERNAME }} + openshift-registry: ${{ secrets.OPENSHIFT_REGISTRY }} + with: + ref: ${{ needs.parse-inputs.outputs.ref }} + directory: jobs/appointment_reminder + image-name: send-appointment-reminder-crond + image-tags: ${{ needs.parse-inputs.outputs.image-tag }} + push-qms: ${{ needs.parse-inputs.outputs.push-qms == 'true' }} + push-theq: ${{ needs.parse-inputs.outputs.push-theq == 'true' }} + + ##### DEPLOY ################################################################# + + tag: + name: Tag + if: github.repository_owner == 'bcgov' + needs: [parse-inputs, appointment-frontend, feedback-api, notifications-api, queue-management-api, queue-management-frontend, send-appointment-reminder-crond] + uses: ./.github/workflows/reusable-tag-image.yaml + secrets: + licence-plate: ${{ needs.parse-inputs.outputs.push-qms == 'true' && secrets.LICENCE_PLATE_QMS || secrets.LICENCE_PLATE_THEQ }} + openshift-api: ${{ secrets.OPENSHIFT_API }} + token: ${{ needs.parse-inputs.outputs.push-qms == 'true' && secrets.SA_PASSWORD_QMS_TOOLS || secrets.SA_PASSWORD_THEQ_TOOLS }} + with: + image-names: appointment-nginx-frontend feedback-api notifications-api queue-management-api queue-management-nginx-frontend send-appointment-reminder-crond + tag-from: ${{ needs.parse-inputs.outputs.image-tag }} + tag-to: ${{ needs.parse-inputs.outputs.environment }} + + wait-for-rollouts: + name: Wait for Rollouts + if: github.repository_owner == 'bcgov' + needs: [parse-inputs, tag] + uses: ./.github/workflows/reusable-wait-for-rollouts.yaml + secrets: + licence-plate: ${{ needs.parse-inputs.outputs.push-qms == 'true' && secrets.LICENCE_PLATE_QMS || secrets.LICENCE_PLATE_THEQ }} + openshift-api: ${{ secrets.OPENSHIFT_API }} + token: ${{ needs.parse-inputs.outputs.push-qms == 'true' && secrets.SA_PASSWORD_QMS_DEV || ( needs.parse-inputs.outputs.environment == 'dev' && secrets.SA_PASSWORD_THEQ_DEV || secrets.SA_PASSWORD_THEQ_TEST ) }} + with: + image-names: appointment-nginx-frontend feedback-api notifications-api queue-management-api queue-management-nginx-frontend send-appointment-reminder-crond-${{ needs.parse-inputs.outputs.environment }} + tag-to: ${{ needs.parse-inputs.outputs.environment }} + + ##### TEST ################################################################### + + # Only run Newman for The Q dev - other environments will fail due to data. + newman-theq-dev: + name: Newman Tests + if: github.event.inputs.namespace != 'The Q Dev' + needs: [parse-inputs, wait-for-rollouts] + runs-on: ubuntu-latest + + steps: + - name: Check out + uses: actions/checkout@v2 + + - name: NPM Install + run: | + cd api/postman + npm install newman + + - name: Run Newman Tests + run: | + cd api/postman + node_modules/newman/bin/newman.js run API_Test_TheQ_Booking.json \ + -e postman_env.json \ + --delay-request 250 \ + --global-var 'auth_url=${{ vars.POSTMAN_AUTH_URL_DEV }}' \ + --global-var 'client_secret=${{ secrets.POSTMAN_CLIENT_SECRET_DEV }}' \ + --global-var 'clientid=${{ vars.POSTMAN_CLIENTID_DEV }}' \ + --global-var 'password=${{ secrets.POSTMAN_PASSWORD }}' \ + --global-var 'password_nonqtxn=${{ secrets.POSTMAN_PASSWORD_NONQTXN }}' \ + --global-var 'public_url=${{ vars.POSTMAN_PUBLIC_API_URL_THEQ_DEV }}' \ + --global-var 'public_user_id=${{ vars.POSTMAN_PUBLIC_USERID }}' \ + --global-var 'public_user_password=${{ secrets.POSTMAN_PASSWORD_PUBLIC_USER }}' \ + --global-var 'realm=${{ vars.POSTMAN_REALM }}' \ + --global-var 'url=${{ vars.POSTMAN_API_URL_THEQ_DEV }}' \ + --global-var 'userid=${{ vars.POSTMAN_USERID }}' \ + --global-var 'userid_nonqtxn=${{ vars.POSTMAN_USERID_NONQTXN }}' + + owasp-staff: + name: OWASP ZAP Scan of Staff Frontend + needs: [parse-inputs, wait-for-rollouts] + runs-on: ubuntu-latest + + steps: + - name: Get Parameters + run: | + if [ ${{ needs.parse-inputs.outputs.push-qms }} == true ]; then + echo "ZAP_URL=${{ secrets.ZAP_STAFFURL_QMS_DEV }}" >> $GITHUB_ENV + elif [ ${{ needs.parse-inputs.outputs.environment }} == dev ]; then + echo "ZAP_URL=${{ secrets.ZAP_STAFFURL_THEQ_DEV }}" >> $GITHUB_ENV + else + echo "ZAP_URL=${{ secrets.ZAP_STAFFURL_THEQ_TEST }}" >> $GITHUB_ENV + fi + + - name: OWASP ZAP Scan + uses: zaproxy/action-full-scan@v0.10.0 + with: + allow_issue_writing: false + cmd_options: '-z "-config scanner.threadPerHost=20"' + target: ${{ env.ZAP_URL }} + + - name: Upload Report as Artifact + uses: actions/upload-artifact@v3 + with: + name: OWASP ZAP - Staff Front End Report + path: report_html.html + + owasp-appointment: + name: OWASP ZAP Scan of Appointment Frontend + needs: [parse-inputs, wait-for-rollouts] + runs-on: ubuntu-latest + + steps: + - name: Get Parameters + run: | + if [ ${{ needs.parse-inputs.outputs.push-qms }} == true ]; then + echo "ZAP_URL=${{ secrets.ZAP_APPTMNTURL_QMS_DEV }}" >> $GITHUB_ENV + elif [ ${{ needs.parse-inputs.outputs.environment }} == dev ]; then + echo "ZAP_URL=${{ secrets.ZAP_APPTMNTURL_THEQ_DEV }}" >> $GITHUB_ENV + else + echo "ZAP_URL=${{ secrets.ZAP_APPTMNTURL_THEQ_TEST }}" >> $GITHUB_ENV + fi + + - name: OWASP ZAP Scan + uses: zaproxy/action-full-scan@v0.10.0 + with: + allow_issue_writing: false + cmd_options: '-z "-config scanner.threadPerHost=20"' + target: ${{ env.ZAP_URL }} + + - name: Upload Report as Artifact + uses: actions/upload-artifact@v3 + with: + name: OWASP ZAP - Appointment Front End Report + path: report_html.html diff --git a/.github/workflows/pull-request-deploy.yaml b/.github/workflows/pull-request-deploy.yaml index b871d4cbe..3f2d97799 100644 --- a/.github/workflows/pull-request-deploy.yaml +++ b/.github/workflows/pull-request-deploy.yaml @@ -85,11 +85,24 @@ jobs: with: ref: ${{ needs.parse-inputs.outputs.ref }} + queue-management-frontend-cypress: + name: Queue Management Frontend Cypress + needs: parse-inputs + uses: ./.github/workflows/reusable-queue-management-frontend-cypress.yaml + secrets: + cypress-project-id: ${{ secrets.CYPRESS_PROJECT_ID }} + cypress-record-key: ${{ secrets.CYPRESS_RECORD_KEY }} + keycloak-auth-url: ${{ secrets.KEYCLOAK_AUTH_URL_DEV }}/auth/ + keycloak-client: ${{ secrets.KEYCLOAK_APPOINTMENTS_FRONTEND_CLIENT }} + keycloak-realm: ${{ secrets.KEYCLOAK_REALM }} + with: + ref: ${{ needs.parse-inputs.outputs.ref }} + ##### BUILD ################################################################## appointment-frontend: name: appointment-frontend - needs: [parse-inputs, appointment-frontend-cypress] + needs: [parse-inputs, queue-management-frontend-cypress, appointment-frontend-cypress] uses: ./.github/workflows/reusable-build-dockerfile.yaml secrets: artifactory-password: ${{ secrets.ARTIFACTORY_PASSWORD }} @@ -112,7 +125,7 @@ jobs: feedback-api: name: feedback-api - needs: [parse-inputs, appointment-frontend-cypress] + needs: [parse-inputs, queue-management-frontend-cypress, appointment-frontend-cypress] uses: ./.github/workflows/reusable-build-s2i.yaml secrets: namespace-theq: ${{ secrets.LICENCE_PLATE_THEQ }}-tools @@ -132,7 +145,7 @@ jobs: notifications-api: name: notifications-api - needs: [parse-inputs, appointment-frontend-cypress] + needs: [parse-inputs, queue-management-frontend-cypress, appointment-frontend-cypress] uses: ./.github/workflows/reusable-build-s2i.yaml secrets: namespace-theq: ${{ secrets.LICENCE_PLATE_THEQ }}-tools @@ -152,7 +165,7 @@ jobs: queue-management-api: name: queue-management-api - needs: [parse-inputs, appointment-frontend-cypress] + needs: [parse-inputs, queue-management-frontend-cypress, appointment-frontend-cypress] uses: ./.github/workflows/reusable-build-s2i.yaml secrets: artifactory-password: ${{ secrets.ARTIFACTORY_PASSWORD }} @@ -175,7 +188,7 @@ jobs: queue-management-frontend: name: queue-management-frontend - needs: [parse-inputs, appointment-frontend-cypress] + needs: [parse-inputs, queue-management-frontend-cypress, appointment-frontend-cypress] uses: ./.github/workflows/reusable-build-dockerfile.yaml secrets: artifactory-password: ${{ secrets.ARTIFACTORY_PASSWORD }} @@ -198,7 +211,7 @@ jobs: send-appointment-reminder-crond: name: send-appointment-reminder-crond - needs: [parse-inputs, appointment-frontend-cypress] + needs: [parse-inputs, queue-management-frontend-cypress, appointment-frontend-cypress] uses: ./.github/workflows/reusable-build-dockerfile.yaml secrets: artifactory-password: ${{ secrets.ARTIFACTORY_PASSWORD }} diff --git a/.github/workflows/reusable-queue-management-frontend-cypress.yaml b/.github/workflows/reusable-queue-management-frontend-cypress.yaml new file mode 100644 index 000000000..6686f084e --- /dev/null +++ b/.github/workflows/reusable-queue-management-frontend-cypress.yaml @@ -0,0 +1,113 @@ +name: Queue Management Frontend Cypress +on: + workflow_call: + inputs: + ref: + required: false + type: string + default: main + secrets: + cypress-project-id: + required: false + cypress-record-key: + required: false + keycloak-auth-url: + required: false + keycloak-client: + required: false + keycloak-realm: + required: false + +jobs: + cypress: + name: Cypress + runs-on: ubuntu-latest + environment: + name: The Q Dev + steps: + - name: Check out + uses: actions/checkout@v2 + with: + ref: ${{ inputs.ref }} + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20.11.1' + + - name: Configuration Files + run: | + + mkdir -p frontend/public/static/keycloak + mkdir -p frontend/public/config + echo "Checking directory existence..." + if [ -d "frontend/public/config/" ]; then + echo "Directory exists." + else + echo "Error: Directory does not exist." + exit 1 + fi + + cat << EOF > frontend/public/config/configuration.json + { + "VUE_APP_API_URL": "http://localhost:5000/api/v1", + "VUE_APP_SOCKET_URL" : "http://localhost:5000", + "BPM_URL": "https://dev-sbc-ffa-bpm.apps.silver.devops.gov.bc.ca/camunda", + "FORM_IO_USER_ROLES": "formsflow-reviewer", + "FORM_IO_API_URL": "https://dev-sbc-ffa-forms.apps.silver.devops.gov.bc.ca", + "FORM_IO_RESOURCE_ID": "6078c70bdb2a9c357e91ba44", + "FORM_IO_REVIEWER_ID": "6078c79cdb2a9c5ce791ba51", + "FORM_IO_REVIEWER": "formsflow-reviewer", + "FORM_FLOW_API_URL" : "https://dev-sbc-ffa-api.apps.silver.devops.gov.bc.ca", + "FORM_FLOW_URL" : "https://dev-sbc-serviceflow.apps.silver.devops.gov.bc.ca", + "SERVICEFLOW_ENABLED": false, + "WEBSOCKET_ENCRYPT_KEY": "123455", + "SOCKET_TIMEOUT": 30000, + "SOCKET_DELAY_MAX" : 3000, + "FORMIO_JWT_SECRET": "12345" + } + EOF + + cat << EOF > frontend/public/static/keycloak/keycloak.json + { + "auth-server-url": "${{ secrets.keycloak-auth-url }}", + "confidential-port": 0, + "public-client": true, + "realm": "${{ secrets.keycloak-realm }}", + "resource": "${{ secrets.keycloak-client }}", + "ssl-required": "external" + } + EOF + + - name: Load Configuration JSON + id: load_config + run: | + echo "Loading environment variables from configuration.json" + # Extract variables from configuration.json and export them + export $(jq -r 'to_entries | .[] | "\(.key)=\(.value)"' frontend/public/config/configuration.json) + # Save variables to $GITHUB_ENV + echo "VUE_APP_API_URL=${VUE_APP_API_URL}" >> $GITHUB_ENV + echo "VUE_APP_SOCKET_URL=${VUE_APP_SOCKET_URL}" >> $GITHUB_ENV + + - name: Cypress Run + uses: cypress-io/github-action@v2 + env: + CYPRESS_PROJECT_ID: ${{ secrets.cypress-project-id }} + CYPRESS_RECORD_KEY: ${{ secrets.cypress-record-key }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VUE_APP_PATH: / + with: + config-file: cypress.config.ts + record: true + start: npm run serve -- --port 8080 + wait-on: http://localhost:8080 + working-directory: frontend + + - name: On Failure Upload Screenshot Artifacts + uses: actions/upload-artifact@v3 + if: failure() + with: + name: queue-management-frontend-cypress + path: | + frontend/cypress/screenshots + frontend/cypress/snapshots/image_snapshot/*/__diff_output__ diff --git a/api/app/admin/office.py b/api/app/admin/office.py index f76b8f6bd..620033a00 100644 --- a/api/app/admin/office.py +++ b/api/app/admin/office.py @@ -68,13 +68,13 @@ def get_query(self): 'appointments_days_limit', 'appointment_duration', 'soonest_appointment', 'max_person_appointment_per_day',\ 'civic_address', 'telephone', 'online_status', 'check_in_notification', 'check_in_reminder_msg',\ 'automatic_reminder_at', 'currently_waiting', 'digital_signage_message', 'digital_signage_message_1',\ - 'digital_signage_message_2', 'digital_signage_message_3', 'show_currently_waiting_bottom' ) + 'digital_signage_message_2', 'digital_signage_message_3', 'show_currently_waiting_bottom', 'optout_status' ) form_edit_rules = ('office_name', 'office_number', 'sb', 'services', 'deleted', 'exams_enabled_ind', 'appointments_enabled_ind', 'timezone', 'latitude', 'longitude', 'office_appointment_message', 'appointments_days_limit', 'appointment_duration', 'soonest_appointment', 'max_person_appointment_per_day',\ 'civic_address', 'telephone', 'online_status', 'check_in_notification', 'check_in_reminder_msg', \ 'automatic_reminder_at', 'currently_waiting', 'digital_signage_message', 'digital_signage_message_1',\ - 'digital_signage_message_2', 'digital_signage_message_3', 'show_currently_waiting_bottom' ) + 'digital_signage_message_2', 'digital_signage_message_3', 'show_currently_waiting_bottom', 'optout_status' ) form_choices = { 'exams_enabled_ind': [ ("0", 'No - Exams are not enabled for this office'), \ @@ -106,6 +106,10 @@ def get_query(self): ("0", 'Off - Hide Currently Waiting at bottom in Smartboard'), \ ("1", 'On - Show Currently Waiting from bottom in Smartboard') ], + 'optout_status': [ + ("0", 'Off - Open tickets automatically closed at end of day'), \ + ("1", 'On - Tickets must be closed manually') + ], } # Defining String constants to appease SonarQube timezone_name_const = 'timezone.timezone_name' @@ -140,6 +144,7 @@ def get_query(self): 'digital_signage_message_2', 'digital_signage_message_3', 'show_currently_waiting_bottom', + 'optout_status', ] column_list_support = ['office_name', @@ -170,6 +175,7 @@ def get_query(self): 'digital_signage_message_2', 'digital_signage_message_3', 'show_currently_waiting_bottom', + 'optout_status', ] form_excluded_columns = ('citizens', @@ -213,6 +219,7 @@ def get_query(self): 'number_of_dlkt', 'timeslots', 'deleted', + 'optout_status', ) form_edit_rules = ('office_name', @@ -249,6 +256,7 @@ def get_query(self): 'number_of_dlkt', 'timeslots', 'deleted', + 'optout_status', ) form_args = { @@ -388,7 +396,8 @@ class OfficeConfigGA(OfficeConfig): 'appointments_days_limit', 'soonest_appointment', 'max_person_appointment_per_day', - 'number_of_dlkt', + 'number_of_dlkt', + 'optout_status' , ) form_excluded_columns = ( diff --git a/api/app/models/theq/office.py b/api/app/models/theq/office.py index e4916819e..4ac1218a1 100644 --- a/api/app/models/theq/office.py +++ b/api/app/models/theq/office.py @@ -81,6 +81,7 @@ class Office(Base): appointment_duration = db.Column(db.Integer, default=30) max_person_appointment_per_day = db.Column(db.Integer, default=1) civic_address = db.Column(db.String(200)) + optout_status = db.Column(db.Integer, default=0) telephone = db.Column(db.String(20)) online_status = db.Column(Enum(Status)) number_of_dlkt = db.Column(db.Integer, nullable=True) diff --git a/api/app/schemas/theq/office_schema.py b/api/app/schemas/theq/office_schema.py index 6dc35dfc6..9dc52d3f9 100644 --- a/api/app/schemas/theq/office_schema.py +++ b/api/app/schemas/theq/office_schema.py @@ -49,6 +49,7 @@ class Meta(BaseSchema.Meta): office_appointment_message = fields.Str() civic_address = fields.Str() online_status = fields.Str() + optout_status = fields.Int() external_map_link = fields.Str() # for walk-in notifications diff --git a/api/migrations/README b/api/migrations/README index 98e4f9c44..c8abd2423 100755 --- a/api/migrations/README +++ b/api/migrations/README @@ -1 +1,15 @@ -Generic single-database configuration. \ No newline at end of file +Generic single-database configuration. + +Run Migrations: + +// make changes to your model +run "flask db migrate -m "ticket_no_description" +OR +run "python manage.py db migrate -m "ticket_no_description" + +//wait for it to generate xyz_ticket_no_description.py in Migrations folder +// check on the auto generated migration script verify/edit changes as required +run "flask db upgrade" to apply changes + +run "flask db downgrade" to rollback the latest changes + diff --git a/api/migrations/versions/4bf4b127aaa4_sbcq155.py b/api/migrations/versions/4bf4b127aaa4_sbcq155.py new file mode 100644 index 000000000..433a06e32 --- /dev/null +++ b/api/migrations/versions/4bf4b127aaa4_sbcq155.py @@ -0,0 +1,33 @@ +"""sbcq155 + +Revision ID: 4bf4b127aaa4 +Revises: ba295f379d0d +Create Date: 2024-07-26 09:41:48.557375 + +""" +from alembic import op +import sqlalchemy as sa +import sqlalchemy_utc +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '4bf4b127aaa4' +down_revision = 'ba295f379d0d' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + + op.add_column('office', sa.Column('optout_status', sa.Integer(), nullable=False, server_default='0')) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + + op.drop_column('office', 'optout_status') + + # ### end Alembic commands ### diff --git a/api/migrations/versions/8b6c67545310_sbcq156.py b/api/migrations/versions/8b6c67545310_sbcq156.py new file mode 100644 index 000000000..8810e7ef8 --- /dev/null +++ b/api/migrations/versions/8b6c67545310_sbcq156.py @@ -0,0 +1,39 @@ +"""sbcq156 + +Revision ID: 8b6c67545310 +Revises: 4bf4b127aaa4 +Create Date: 2024-07-26 10:13:02.148021 + +""" +from alembic import op +import sqlalchemy as sa +import sqlalchemy_utc +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '8b6c67545310' +down_revision = '4bf4b127aaa4' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + + op.alter_column('office', 'optout_status', + existing_type=sa.INTEGER(), + nullable=True, + existing_server_default=sa.text('0')) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + + op.alter_column('office', 'optout_status', + existing_type=sa.INTEGER(), + nullable=False, + existing_server_default=sa.text('0')) + + # ### end Alembic commands ### diff --git a/api/qsystem.py b/api/qsystem.py index d9770f899..a3cb4a72a 100644 --- a/api/qsystem.py +++ b/api/qsystem.py @@ -24,6 +24,7 @@ from sqlalchemy import event from sqlalchemy.engine import Engine from sqlalchemy_continuum import make_versioned +from flask_migrate import Migrate def my_print(my_data): @@ -46,6 +47,8 @@ def time_string(): ms = now.strftime("%f")[:3] now_string = now.strftime("%Y-%m-%d %H:%M:%S,") return "[" + now_string + ms + "] " +migrate = Migrate() + application = Flask(__name__, instance_relative_config=True) @@ -74,6 +77,7 @@ def time_string(): cache.init_app(application) ma = Marshmallow(application) +migrate.init_app(application, db) make_versioned(user_cls=None, plugins=[]) diff --git a/api/sqlcode/Q2-CloseOpenTicketsFunctionDefinition.sql b/api/sqlcode/Q2-CloseOpenTicketsFunctionDefinition.sql index 8193fc53c..76d166e2c 100644 --- a/api/sqlcode/Q2-CloseOpenTicketsFunctionDefinition.sql +++ b/api/sqlcode/Q2-CloseOpenTicketsFunctionDefinition.sql @@ -64,7 +64,7 @@ begin set cs_id = id_got_services, accurate_time_ind = 0, citizen_comments = '' - where citizen.citizen_id in + where office_id in (select office_id from office where optout_status = 0) and citizen.citizen_id in ( select distinct f.cid from @@ -94,19 +94,33 @@ begin set cs_id = id_got_services, accurate_time_ind = 0, citizen_comments = '' - where cs_id = (select cs_id from citizenstate where cs_state_name = 'Active'); + where cs_id = (select cs_id from citizenstate where cs_state_name = 'Active') and office_id in (select office_id from office where optout_status = 0); get diagnostics update_active = ROW_COUNT; /* Set period time_end to be now for all periods with null ending time. */ - update period - set time_end = now() - where time_end is null; + + UPDATE period + SET time_end = NOW() + WHERE time_end IS NULL + AND sr_id IN ( + SELECT sr_id + FROM servicereq + WHERE citizen_id IN ( + SELECT citizen_id + FROM citizen + WHERE office_id in (select office_id from office where optout_status = 0) + )); get diagnostics update_period = ROW_COUNT; /* Set all service requests to be complete, for those that aren't marked as complete. */ update servicereq set sr_state_id = (select sr_state_id from srstate where sr_code = 'Complete') - where sr_state_id != (select sr_state_id from srstate where sr_code = 'Complete'); + where sr_state_id != (select sr_state_id from srstate where sr_code = 'Complete') and + citizen_id in ( + SELECT citizen_id + FROM citizen + WHERE office_id in (select office_id from office where optout_status = 0)); + get diagnostics update_sr = ROW_COUNT; return_message = 'Tickets closed first pass: ' || update_citizen::text diff --git a/api/version.py b/api/version.py index 3509bd885..64778fc22 100644 --- a/api/version.py +++ b/api/version.py @@ -1 +1 @@ -__version__ = '1.1.3' \ No newline at end of file +__version__ = '1.1.4' \ No newline at end of file diff --git a/frontend/cypress.config.ts b/frontend/cypress.config.ts new file mode 100644 index 000000000..d2f4d6601 --- /dev/null +++ b/frontend/cypress.config.ts @@ -0,0 +1,37 @@ +import { defineConfig } from "cypress"; +import { addMatchImageSnapshotPlugin } from '@simonsmith/cypress-image-snapshot/plugin' + + +export default defineConfig({ + chromeWebSecurity: true, + env: { + 'cypress-plugin-snapshots': { + imageConfig: { + threshold: 0, + thresholdType: 'percent' + }, + screenshotConfig: { + blackout: [], + capture: 'fullPage', + clip: null, + disableTimersAndAnimations: true, + log: false, + scale: false, + timeout: 30000 + } + } + }, + video: false, + e2e: { + setupNodeEvents(on, config) { + // implement node event listeners here + addMatchImageSnapshotPlugin(on) + }, + baseUrl: 'http://localhost:8080', + excludeSpecPattern: ['**/__snapshots__/*', '**/__image_snapshots__/*'], + retries: { + runMode: 4, + openMode: 4 + } + } +}) diff --git a/frontend/cypress/e2e/image_snapshot/step1.cy.ts b/frontend/cypress/e2e/image_snapshot/step1.cy.ts new file mode 100644 index 000000000..7e4f12492 --- /dev/null +++ b/frontend/cypress/e2e/image_snapshot/step1.cy.ts @@ -0,0 +1,14 @@ +import { API_PREFIX } from '../../support/e2e' + +describe('Login page', () => { + + beforeEach( () => { + cy.fixture('offices').then((json) => { + cy.intercept('GET', API_PREFIX + 'offices/', json) + }) + }) + it('should match screenshot of element', () => { + cy.visit('/') + cy.matchImageSnapshot() + }) +}) \ No newline at end of file diff --git a/frontend/cypress/fixtures/example.json b/frontend/cypress/fixtures/example.json new file mode 100644 index 000000000..02e425437 --- /dev/null +++ b/frontend/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/frontend/cypress/fixtures/offices.json b/frontend/cypress/fixtures/offices.json new file mode 100644 index 000000000..37528a2cd --- /dev/null +++ b/frontend/cypress/fixtures/offices.json @@ -0,0 +1,307 @@ +{ + "offices": [ + { + "office_id": 6, + "office_name": "100 Mile House", + "office_number": 61, + "sb_id": 6, + "deleted": null, + "exams_enabled_ind": 0, + "appointments_enabled_ind": 1, + "max_person_appointment_per_day": 1, + "telephone": "999-999-9999", + "appointments_days_limit": 30, + "appointment_duration": 30, + "sb": { + "sb_id": 6, + "sb_type": "nocallonsmartboard" + }, + "counters": [ + { + "counter_id": 1, + "counter_name": "Quick Trans" + }, + { + "counter_id": 2, + "counter_name": "Counter" + } + ], + "quick_list": [], + "back_office_list": [], + "timezone": { + "timezone_id": 8, + "timezone_name": "America/Creston" + }, + "timeslots": [ + { + "start_time": "09:00:00", + "end_time": "10:00:00", + "day_of_week": [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday" + ], + "no_of_slots": 1, + "office": 6 + }, + { + "start_time": "10:30:00", + "end_time": "11:00:00", + "day_of_week": [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday" + ], + "no_of_slots": 1, + "office": 6 + }, + { + "start_time": "13:00:00", + "end_time": "13:30:00", + "day_of_week": [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday" + ], + "no_of_slots": 1, + "office": 6 + }, + { + "start_time": "13:30:00", + "end_time": "16:30:00", + "day_of_week": [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday" + ], + "no_of_slots": 0, + "office": 6 + } + ], + "latitude": 51.644, + "longitude": -121.295, + "office_appointment_message": null, + "civic_address": "100 Mile House, BC", + "online_status": "Status.SHOW", + "external_map_link": null, + "check_in_notification": null, + "check_in_reminder_msg": null, + "automatic_reminder_at": null, + "currently_waiting": null, + "digital_signage_message": null, + "digital_signage_message_1": null, + "digital_signage_message_2": null, + "digital_signage_message_3": null, + "show_currently_waiting_bottom": null + }, + { + "office_id": 8, + "office_name": "Pesticide Offsite", + "office_number": 997, + "sb_id": 4, + "deleted": null, + "exams_enabled_ind": 1, + "appointments_enabled_ind": 0, + "max_person_appointment_per_day": 1, + "telephone": null, + "appointments_days_limit": 30, + "appointment_duration": 30, + "sb": { + "sb_id": 4, + "sb_type": "callbyname" + }, + "counters": [ + { + "counter_id": 1, + "counter_name": "Quick Trans" + }, + { + "counter_id": 2, + "counter_name": "Counter" + } + ], + "quick_list": [], + "back_office_list": [], + "timezone": { + "timezone_id": 5, + "timezone_name": "America/Vancouver" + }, + "timeslots": [], + "latitude": null, + "longitude": null, + "office_appointment_message": null, + "civic_address": null, + "online_status": "Status.HIDE", + "external_map_link": null, + "check_in_notification": null, + "check_in_reminder_msg": null, + "automatic_reminder_at": null, + "currently_waiting": null, + "digital_signage_message": null, + "digital_signage_message_1": null, + "digital_signage_message_2": null, + "digital_signage_message_3": null, + "show_currently_waiting_bottom": null + }, + { + "office_id": 5, + "office_name": "Test Office", + "office_number": 999, + "sb_id": 5, + "deleted": null, + "exams_enabled_ind": 1, + "appointments_enabled_ind": 1, + "max_person_appointment_per_day": 10, + "telephone": "999-999-9999", + "appointments_days_limit": 30, + "appointment_duration": 30, + "sb": { + "sb_id": 5, + "sb_type": "callbyticket" + }, + "counters": [ + { + "counter_id": 1, + "counter_name": "Quick Trans" + }, + { + "counter_id": 2, + "counter_name": "Counter" + } + ], + "quick_list": [], + "back_office_list": [], + "timezone": { + "timezone_id": 5, + "timezone_name": "America/Vancouver" + }, + "timeslots": [], + "latitude": 48.458359, + "longitude": -123.377106, + "office_appointment_message": "Test Message", + "civic_address": "4000 Seymour", + "online_status": "Status.SHOW", + "external_map_link": null, + "check_in_notification": null, + "check_in_reminder_msg": null, + "automatic_reminder_at": null, + "currently_waiting": null, + "digital_signage_message": null, + "digital_signage_message_1": null, + "digital_signage_message_2": null, + "digital_signage_message_3": null, + "show_currently_waiting_bottom": null + }, + { + "office_id": 7, + "office_name": "Victoria", + "office_number": 94, + "sb_id": 4, + "deleted": null, + "exams_enabled_ind": 0, + "appointments_enabled_ind": 1, + "max_person_appointment_per_day": 1, + "telephone": "999-999-9999", + "appointments_days_limit": 30, + "appointment_duration": 30, + "sb": { + "sb_id": 4, + "sb_type": "callbyname" + }, + "counters": [ + { + "counter_id": 1, + "counter_name": "Quick Trans" + }, + { + "counter_id": 2, + "counter_name": "Counter" + } + ], + "quick_list": [], + "back_office_list": [], + "timezone": { + "timezone_id": 5, + "timezone_name": "America/Vancouver" + }, + "timeslots": [ + { + "start_time": "09:00:00", + "end_time": "09:45:00", + "day_of_week": [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday" + ], + "no_of_slots": 1, + "office": 7 + }, + { + "start_time": "10:30:00", + "end_time": "11:15:00", + "day_of_week": [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday" + ], + "no_of_slots": 1, + "office": 7 + }, + { + "start_time": "11:00:00", + "end_time": "11:45:00", + "day_of_week": [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday" + ], + "no_of_slots": 1, + "office": 7 + }, + { + "start_time": "15:00:00", + "end_time": "15:45:00", + "day_of_week": [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday" + ], + "no_of_slots": 1, + "office": 7 + } + ], + "latitude": 51.644, + "longitude": -121.295, + "office_appointment_message": null, + "civic_address": "Victoria, BC", + "online_status": "Status.SHOW", + "external_map_link": null, + "check_in_notification": null, + "check_in_reminder_msg": null, + "automatic_reminder_at": null, + "currently_waiting": null, + "digital_signage_message": null, + "digital_signage_message_1": null, + "digital_signage_message_2": null, + "digital_signage_message_3": null, + "show_currently_waiting_bottom": null + } + ], + "errors": {} +} diff --git a/frontend/cypress/snapshots/image_snapshot/step1.cy.ts/Login page -- should match screenshot of element.snap.png b/frontend/cypress/snapshots/image_snapshot/step1.cy.ts/Login page -- should match screenshot of element.snap.png new file mode 100644 index 000000000..a71409ab7 Binary files /dev/null and b/frontend/cypress/snapshots/image_snapshot/step1.cy.ts/Login page -- should match screenshot of element.snap.png differ diff --git a/frontend/cypress/support/commands.ts b/frontend/cypress/support/commands.ts new file mode 100644 index 000000000..c1433a8cc --- /dev/null +++ b/frontend/cypress/support/commands.ts @@ -0,0 +1,47 @@ +/// +// *********************************************** +// This example commands.ts shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) +// +// declare global { +// namespace Cypress { +// interface Chainable { +// login(email: string, password: string): Chainable +// drag(subject: string, options?: Partial): Chainable +// dismiss(subject: string, options?: Partial): Chainable +// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable +// } +// } +// } + +import {addMatchImageSnapshotCommand} from '@simonsmith/cypress-image-snapshot/command' + +addMatchImageSnapshotCommand() + +// can also add any default options to be used +// by all instances of `matchImageSnapshot` +addMatchImageSnapshotCommand({ + failureThreshold: 0.2 +}) diff --git a/frontend/cypress/support/e2e.ts b/frontend/cypress/support/e2e.ts new file mode 100644 index 000000000..26201c7d5 --- /dev/null +++ b/frontend/cypress/support/e2e.ts @@ -0,0 +1,18 @@ +// *********************************************************** +// This example support/e2e.ts is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +import './commands' + +export const API_PREFIX = 'http://localhost:5000/api/v1/' diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7ebd0f81d..8f219d7d8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "theq", - "version": "2.1.8", + "version": "2.1.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "theq", - "version": "2.1.8", + "version": "2.1.9", "hasInstallScript": true, "dependencies": { "@babel/polyfill": "^7.12.1", @@ -77,6 +77,7 @@ "@babel/plugin-transform-runtime": "7.13.10", "@babel/preset-env": "7.13.12", "@babel/register": "7.13.14", + "@simonsmith/cypress-image-snapshot": "^9.1.0", "@types/jest": "^27.5.2", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", @@ -94,6 +95,7 @@ "babel-loader": "8.2.2", "chalk": "4.1.0", "connect-history-api-fallback": "1.6.0", + "cypress": "^12.17.4", "eslint": "^7.32.0", "eslint-friendly-formatter": "4.0.1", "eslint-loader": "4.0.2", @@ -2030,6 +2032,122 @@ "dev": true, "license": "MIT" }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cypress/request": { + "version": "2.88.12", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.12.tgz", + "integrity": "sha512-tOn+0mDZxASFM+cuAP9szGUGPI1HwWVSvdzm7V4cCsPdFTx6qMj29CwaQmRAMIEhORIUBFBsYROYJcveK4uOjA==", + "dev": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "http-signature": "~1.3.6", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "performance-now": "^2.1.0", + "qs": "~6.10.3", + "safe-buffer": "^5.1.2", + "tough-cookie": "^4.1.3", + "tunnel-agent": "^0.6.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@cypress/request/node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/@cypress/request/node_modules/http-signature": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", + "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.14.1" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/@cypress/request/node_modules/jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "node_modules/@cypress/request/node_modules/qs": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", + "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@cypress/xvfb": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", + "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", + "dev": true, + "dependencies": { + "debug": "^3.1.0", + "lodash.once": "^4.1.1" + } + }, + "node_modules/@cypress/xvfb/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -2560,9 +2678,9 @@ } }, "node_modules/@jest/expect/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "dev": true, "license": "MIT", "dependencies": { @@ -3247,6 +3365,36 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@simonsmith/cypress-image-snapshot": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@simonsmith/cypress-image-snapshot/-/cypress-image-snapshot-9.1.0.tgz", + "integrity": "sha512-Lme+POO6mQi9eXEe3OB/6mBhdb73qbDgE/VkSCNZWVQlCt6wOOZCjVKCvfO8mPPmKjnEiePzSTSjTvpwDSogBg==", + "dev": true, + "dependencies": { + "@types/jest-image-snapshot": "^6.1.0", + "chalk": "^4.1.2", + "jest-image-snapshot": "^6.1.0" + }, + "peerDependencies": { + "cypress": ">10.0.0" + } + }, + "node_modules/@simonsmith/cypress-image-snapshot/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -3557,6 +3705,17 @@ "pretty-format": "^27.0.0" } }, + "node_modules/@types/jest-image-snapshot": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@types/jest-image-snapshot/-/jest-image-snapshot-6.4.0.tgz", + "integrity": "sha512-8TQ/EgqFCX0UWSpH488zAc21fCkJNpZPnnp3xWFMqElxApoJV5QOoqajnVRV7AhfF0rbQWTVyc04KG7tXnzCPA==", + "dev": true, + "dependencies": { + "@types/jest": "*", + "@types/pixelmatch": "*", + "ssim.js": "^3.1.1" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -3617,6 +3776,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/pixelmatch": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/@types/pixelmatch/-/pixelmatch-5.2.6.tgz", + "integrity": "sha512-wC83uexE5KGuUODn6zkm9gMzTwdY5L0chiK+VrKcDfEjzxh1uadlWTvOmAbCpnM9zx/Ww3f8uKlYQVnO/TrqVg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/prettier": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", @@ -3678,6 +3846,18 @@ "@types/send": "*" } }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", + "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", + "dev": true + }, + "node_modules/@types/sizzle": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", + "integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==", + "dev": true + }, "node_modules/@types/sockjs": { "version": "0.3.36", "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", @@ -4477,9 +4657,9 @@ } }, "node_modules/@vue/cli-service/node_modules/autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", "dev": true, "funding": [ { @@ -4497,11 +4677,11 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -4636,39 +4816,39 @@ "license": "ISC" }, "node_modules/@vue/compiler-core": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.35.tgz", - "integrity": "sha512-gKp0zGoLnMYtw4uS/SJRRO7rsVggLjvot3mcctlMXunYNsX+aRJDqqw/lV5/gHK91nvaAAlWFgdVl020AW1Prg==", + "version": "3.4.37", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.37.tgz", + "integrity": "sha512-ZDDT/KiLKuCRXyzWecNzC5vTcubGz4LECAtfGPENpo0nrmqJHwuWtRLxk/Sb9RAKtR9iFflFycbkjkY+W/PZUQ==", "license": "MIT", "dependencies": { "@babel/parser": "^7.24.7", - "@vue/shared": "3.4.35", - "entities": "^4.5.0", + "@vue/shared": "3.4.37", + "entities": "^5.0.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.35.tgz", - "integrity": "sha512-pWIZRL76/oE/VMhdv/ovZfmuooEni6JPG1BFe7oLk5DZRo/ImydXijoZl/4kh2406boRQ7lxTYzbZEEXEhj9NQ==", + "version": "3.4.37", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.37.tgz", + "integrity": "sha512-rIiSmL3YrntvgYV84rekAtU/xfogMUJIclUMeIKEtVBFngOL3IeZHhsH3UaFEgB5iFGpj6IW+8YuM/2Up+vVag==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.4.35", - "@vue/shared": "3.4.35" + "@vue/compiler-core": "3.4.37", + "@vue/shared": "3.4.37" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.35.tgz", - "integrity": "sha512-xacnRS/h/FCsjsMfxBkzjoNxyxEyKyZfBch/P4vkLRvYJwe5ChXmZZrj8Dsed/752H2Q3JE8kYu9Uyha9J6PgA==", + "version": "3.4.37", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.37.tgz", + "integrity": "sha512-vCfetdas40Wk9aK/WWf8XcVESffsbNkBQwS5t13Y/PcfqKfIwJX2gF+82th6dOpnpbptNMlMjAny80li7TaCIg==", "license": "MIT", "dependencies": { "@babel/parser": "^7.24.7", - "@vue/compiler-core": "3.4.35", - "@vue/compiler-dom": "3.4.35", - "@vue/compiler-ssr": "3.4.35", - "@vue/shared": "3.4.35", + "@vue/compiler-core": "3.4.37", + "@vue/compiler-dom": "3.4.37", + "@vue/compiler-ssr": "3.4.37", + "@vue/shared": "3.4.37", "estree-walker": "^2.0.2", "magic-string": "^0.30.10", "postcss": "^8.4.40", @@ -4676,13 +4856,13 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.35.tgz", - "integrity": "sha512-7iynB+0KB1AAJKk/biENTV5cRGHRdbdaD7Mx3nWcm1W8bVD6QmnH3B4AHhQQ1qZHhqFwzEzMwiytXm3PX1e60A==", + "version": "3.4.37", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.37.tgz", + "integrity": "sha512-TyAgYBWrHlFrt4qpdACh8e9Ms6C/AZQ6A6xLJaWrCL8GCX5DxMzxyeFAEMfU/VFr4tylHm+a2NpfJpcd7+20XA==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.4.35", - "@vue/shared": "3.4.35" + "@vue/compiler-dom": "3.4.37", + "@vue/shared": "3.4.37" } }, "node_modules/@vue/component-compiler-utils": { @@ -4800,9 +4980,9 @@ } }, "node_modules/@vue/shared": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.35.tgz", - "integrity": "sha512-hvuhBYYDe+b1G8KHxsQ0diDqDMA8D9laxWZhNAjE83VZb5UDaXl9Xnz7cGdDSyiHM90qqI/CyGMcpBpiDy6VVQ==", + "version": "3.4.37", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.37.tgz", + "integrity": "sha512-nIh8P2fc3DflG8+5Uw8PT/1i17ccFn0xxN/5oE9RfV5SVnd7G0XEFRwakrnNFE/jlS95fpGXDVG5zDETS26nmg==", "license": "MIT" }, "node_modules/@vue/test-utils": { @@ -6358,6 +6538,12 @@ "node": ">= 6" } }, + "node_modules/blob-util": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", + "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", + "dev": true + }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -6613,9 +6799,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", - "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "funding": [ { "type": "opencollective", @@ -6632,10 +6818,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001629", - "electron-to-chromium": "^1.4.796", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.16" + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -6814,6 +7000,15 @@ "dev": true, "license": "ISC" }, + "node_modules/cachedir": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", + "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -6950,9 +7145,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001638", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001638.tgz", - "integrity": "sha512-5SuJUJ7cZnhPpeLHaH0c/HPAnAHZvS6ElWyHK9GSIbVOQABLzowiI2pjmpvZ1WEbkyz46iFd4UXlOHR5SqgfMQ==", + "version": "1.0.30001651", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", + "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", "funding": [ { "type": "opencollective", @@ -7012,6 +7207,15 @@ "node": ">=10" } }, + "node_modules/check-more-types": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", + "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -7172,6 +7376,51 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/clipboardy": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz", @@ -7314,6 +7563,15 @@ "node": ">= 12" } }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -7581,9 +7839,9 @@ } }, "node_modules/core-js": { - "version": "3.37.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", - "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==", + "version": "3.38.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.38.0.tgz", + "integrity": "sha512-XPpwqEodRljce9KswjZShh95qJ1URisBeKCjUdq27YdenkslVe7OO0ZJhlYXAChW7OhXaRLl8AAba7IBfoIHug==", "hasInstallScript": true, "license": "MIT", "funding": { @@ -7884,9 +8142,9 @@ } }, "node_modules/create-jest/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "dev": true, "license": "MIT", "dependencies": { @@ -8965,43 +9223,289 @@ "dev": true, "license": "MIT" }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", + "license": "MIT" + }, + "node_modules/custom-event-polyfill": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz", + "integrity": "sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==", + "license": "MIT" + }, + "node_modules/cypress": { + "version": "12.17.4", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.17.4.tgz", + "integrity": "sha512-gAN8Pmns9MA5eCDFSDJXWKUpaL3IDd89N9TtIupjYnzLSmlpVr+ZR+vb4U/qaMp+lB6tBvAmt7504c3Z4RU5KQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@cypress/request": "2.88.12", + "@cypress/xvfb": "^1.2.4", + "@types/node": "^16.18.39", + "@types/sinonjs__fake-timers": "8.1.1", + "@types/sizzle": "^2.3.2", + "arch": "^2.2.0", + "blob-util": "^2.0.2", + "bluebird": "^3.7.2", + "buffer": "^5.6.0", + "cachedir": "^2.3.0", + "chalk": "^4.1.0", + "check-more-types": "^2.24.0", + "cli-cursor": "^3.1.0", + "cli-table3": "~0.6.1", + "commander": "^6.2.1", + "common-tags": "^1.8.0", + "dayjs": "^1.10.4", + "debug": "^4.3.4", + "enquirer": "^2.3.6", + "eventemitter2": "6.4.7", + "execa": "4.1.0", + "executable": "^4.1.1", + "extract-zip": "2.0.1", + "figures": "^3.2.0", + "fs-extra": "^9.1.0", + "getos": "^3.2.1", + "is-ci": "^3.0.0", + "is-installed-globally": "~0.4.0", + "lazy-ass": "^1.6.0", + "listr2": "^3.8.3", + "lodash": "^4.17.21", + "log-symbols": "^4.0.0", + "minimist": "^1.2.8", + "ospath": "^1.2.2", + "pretty-bytes": "^5.6.0", + "process": "^0.11.10", + "proxy-from-env": "1.0.0", + "request-progress": "^3.0.0", + "semver": "^7.5.3", + "supports-color": "^8.1.1", + "tmp": "~0.2.1", + "untildify": "^4.0.0", + "yauzl": "^2.10.0" + }, + "bin": { + "cypress": "bin/cypress" + }, + "engines": { + "node": "^14.0.0 || ^16.0.0 || >=18.0.0" + } + }, + "node_modules/cypress/node_modules/@types/node": { + "version": "16.18.103", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.103.tgz", + "integrity": "sha512-gOAcUSik1nR/CRC3BsK8kr6tbmNIOTpvb1sT+v5Nmmys+Ho8YtnIHP90wEsVK4hTcHndOqPVIlehEGEA5y31bA==", + "dev": true + }, + "node_modules/cypress/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/cypress/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/cypress/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/cypress/node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/cypress/node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cypress/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cypress/node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "engines": { + "node": ">=8.12.0" + } + }, + "node_modules/cypress/node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/cypress/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cypress/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, - "license": "MIT", "dependencies": { - "cssom": "~0.3.6" + "path-key": "^3.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true, - "license": "MIT" + "node_modules/cypress/node_modules/proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", + "dev": true }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" + "node_modules/cypress/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } }, - "node_modules/custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", - "license": "MIT" + "node_modules/cypress/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } }, - "node_modules/custom-event-polyfill": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz", - "integrity": "sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==", - "license": "MIT" + "node_modules/cypress/node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, + "engines": { + "node": ">=14.14" + } }, "node_modules/d": { "version": "1.0.2", @@ -9104,6 +9608,12 @@ "integrity": "sha512-/+lyMUKoRogMuTeOVii6lUwjbVlesN9YRYLzZT/g3TEZ3uD9QnpjResujeEqUW+OSNbT7T1+SYdyEkTcRv+KDQ==", "license": "MIT" }, + "node_modules/dayjs": { + "version": "1.11.12", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz", + "integrity": "sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg==", + "dev": true + }, "node_modules/de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", @@ -9866,9 +10376,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.4.812", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.812.tgz", - "integrity": "sha512-7L8fC2Ey/b6SePDFKR2zHAy4mbdp1/38Yk5TsARO66W3hC5KEaeKMMHoxwtuH+jcu2AYLSn9QX04i95t6Fl1Hg==", + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.6.tgz", + "integrity": "sha512-jwXWsM5RPf6j9dPYzaorcBSUg6AiqocPEyMpkchkvntaH9HGfOOMZwxMJjDY/XEs3T5dM7uyH1VhRMkqUU9qVw==", "license": "ISC" }, "node_modules/elliptic": { @@ -10037,9 +10547,9 @@ } }, "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-5.0.0.tgz", + "integrity": "sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -11460,6 +11970,12 @@ "node": ">=4.0.0" } }, + "node_modules/eventemitter2": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", + "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", + "dev": true + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -11593,6 +12109,27 @@ "which": "bin/which" } }, + "node_modules/executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "dev": true, + "dependencies": { + "pify": "^2.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/executable/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -12570,6 +13107,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/getos": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", + "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", + "dev": true, + "dependencies": { + "async": "^3.2.0" + } + }, + "node_modules/getos/node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true + }, "node_modules/getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -12630,6 +13182,30 @@ "process": "~0.5.1" } }, + "node_modules/global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "dev": true, + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/global-dirs/node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/global/node_modules/process": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", @@ -12713,6 +13289,12 @@ "node": "*" } }, + "node_modules/glur": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/glur/-/glur-1.1.2.tgz", + "integrity": "sha512-l+8esYHTKOx2G/Aao4lEQ0bnHWg4fWtJbVoZZT9Knxi01pB8C80BR85nONLFwkkQoFRCmXY+BUcGZN3yZ2QsRA==", + "dev": true + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -13355,9 +13937,9 @@ "license": "BSD-3-Clause" }, "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "engines": { @@ -13828,6 +14410,22 @@ "node": ">=0.10.0" } }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -13883,6 +14481,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", @@ -14679,9 +15286,9 @@ } }, "node_modules/jest-cli/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "dev": true, "license": "MIT", "dependencies": { @@ -15681,6 +16288,55 @@ "fsevents": "^2.3.2" } }, + "node_modules/jest-image-snapshot": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/jest-image-snapshot/-/jest-image-snapshot-6.4.0.tgz", + "integrity": "sha512-IWGtSOnelwaVPd09STbJuLmnAwlBC/roJtTLGLb8M3TA0vfku3MRNEXmljTa1EMXqdRbA0oIWiqHFB1ttTGazQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "get-stdin": "^5.0.1", + "glur": "^1.1.2", + "lodash": "^4.17.4", + "pixelmatch": "^5.1.0", + "pngjs": "^3.4.0", + "rimraf": "^2.6.2", + "ssim.js": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "jest": ">=20 <=29" + }, + "peerDependenciesMeta": { + "jest": { + "optional": true + } + } + }, + "node_modules/jest-image-snapshot/node_modules/get-stdin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", + "integrity": "sha512-jZV7n6jGE3Gt7fgSTJoz91Ak5MuTLwMwkoYdjxuJ/AmjIsE1UC03y/IWkZCQGEvVNS9qoRNwy5BCqxImv0FVeA==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jest-image-snapshot/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/jest-jasmine2": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz", @@ -16176,9 +16832,9 @@ "license": "MIT" }, "node_modules/jest-watch-typeahead/node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "dev": true, "license": "MIT", "dependencies": { @@ -17019,6 +17675,15 @@ "launch-editor": "^2.8.0" } }, + "node_modules/lazy-ass": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", + "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", + "dev": true, + "engines": { + "node": "> 0.8" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -17060,6 +17725,65 @@ "dev": true, "license": "MIT" }, + "node_modules/listr2": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", + "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", + "dev": true, + "dependencies": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.16", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.5.1", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } + } + }, + "node_modules/listr2/node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/log-update/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -18411,9 +19135,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "license": "MIT" }, "node_modules/node-sass": { @@ -18918,6 +19642,12 @@ "node": ">=0.10.0" } }, + "node_modules/ospath": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", + "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", + "dev": true + }, "node_modules/p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -19398,6 +20128,27 @@ "node": ">= 6" } }, + "node_modules/pixelmatch": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", + "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", + "dev": true, + "dependencies": { + "pngjs": "^6.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/pixelmatch/node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "dev": true, + "engines": { + "node": ">=12.13.0" + } + }, "node_modules/pkcs7": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-0.2.3.tgz", @@ -19474,6 +20225,15 @@ "node": ">=4" } }, + "node_modules/pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/popper.js": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", @@ -19542,9 +20302,9 @@ } }, "node_modules/postcss": { - "version": "8.4.40", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz", - "integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==", + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", "funding": [ { "type": "opencollective", @@ -20175,6 +20935,18 @@ "node": ">=0.10.0" } }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pretty-error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", @@ -20960,6 +21732,15 @@ "node": ">= 6" } }, + "node_modules/request-progress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", + "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", + "dev": true, + "dependencies": { + "throttleit": "^1.0.0" + } + }, "node_modules/request/node_modules/form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", @@ -21127,6 +21908,12 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -21196,6 +21983,15 @@ "individual": "^2.0.0" } }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-array-concat": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", @@ -22157,6 +22953,12 @@ "dev": true, "license": "MIT" }, + "node_modules/ssim.js": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/ssim.js/-/ssim.js-3.5.0.tgz", + "integrity": "sha512-Aj6Jl2z6oDmgYFFbQqK7fght19bXdOxY7Tj03nF+03M9gCBAjeIiO8/PlEGMfKDwYpw4q6iBqVq2YuREorGg/g==", + "dev": true + }, "node_modules/ssri": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", @@ -22908,6 +23710,15 @@ "dev": true, "license": "MIT" }, + "node_modules/throttleit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", + "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -23562,10 +24373,19 @@ "node": ">= 0.8" } }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/update-browserslist-db": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", - "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "funding": [ { "type": "opencollective", diff --git a/frontend/package.json b/frontend/package.json index 8a6111722..f873c4a97 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,12 +1,14 @@ { "name": "theq", - "version": "2.1.8", + "version": "2.1.9", "private": true, "description": "Theq", "author": "Scott Rumsby ", "scripts": { "serve": "NODE_OPTIONS=--openssl-legacy-provider vue-cli-service serve", "build": "NODE_OPTIONS=--openssl-legacy-provider vue-cli-service build --mode production", + "cy": "cypress run --config-file=cypress.config.ts", + "cy:open": "cypress open --config-file=cypress.config.ts", "test:unit": "NODE_OPTIONS=--openssl-legacy-provider vue-cli-service test:unit", "lint": "NODE_OPTIONS=--openssl-legacy-provider vue-cli-service lint", "prebuild": "npm run update-version", @@ -85,6 +87,7 @@ "@babel/plugin-transform-runtime": "7.13.10", "@babel/preset-env": "7.13.12", "@babel/register": "7.13.14", + "@simonsmith/cypress-image-snapshot": "^9.1.0", "@types/jest": "^27.5.2", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", @@ -102,6 +105,7 @@ "babel-loader": "8.2.2", "chalk": "4.1.0", "connect-history-api-fallback": "1.6.0", + "cypress": "^12.17.4", "eslint": "^7.32.0", "eslint-friendly-formatter": "4.0.1", "eslint-loader": "4.0.2", diff --git a/frontend/src/components/AddCitizen/form-components/channel.vue b/frontend/src/components/AddCitizen/form-components/channel.vue index 9363750d7..b3aaf68ac 100644 --- a/frontend/src/components/AddCitizen/form-components/channel.vue +++ b/frontend/src/components/AddCitizen/form-components/channel.vue @@ -2,7 +2,7 @@ - Channel: + Channel: - Comments: + Comments: - Notification: + Notification: - + Filter Appointments - Step 1: Select Event Type + Step 1: Select Event Type @@ -99,6 +99,7 @@ v-b-toggle.collapse-single-event @click="setRecurring" size="lg" + id="create-single-blackout" > Create Single Blackout @@ -110,6 +111,7 @@ v-b-toggle.collapse-recurring-events @click="setSingle" size="lg" + id="create-recurring-blackout" > Create Recurring Blackout @@ -123,6 +125,7 @@ size="lg" v-b-toggle.collapse-recurring-stat @click="setSTAT" + id="create-stat" > Create STAT @@ -141,18 +144,18 @@ - User Name - + User Name + - Contact Information (optional) - + Contact Information (optional) + - Blackout Date: + Blackout Date: - Blackout Start Time: + Blackout Start Time: - Blackout End Time: + Blackout End Time: Recurring Event - Step 2: Event Information + Step 2: Event Information - User Name: - + User Name: + - Contact Information (optional): - + Contact Information (optional): + - Blackout Start Time: + Blackout Start Time: - Blackout End Time: + Blackout End Time: - Blackout Start Date: + Blackout Start Date: - Blackout End Date: + Blackout End Date: - Frequency: + + Frequency: Weekly Daily + - Select Weekdays: + + Select Weekdays: Thurs. Fri. + - Number of Occurences(optional): Recurring STAT - Step 2: STAT Information + Step 2: STAT Information - User Name: - + User Name: + - Contact Information (optional): - + Contact Information (optional): + - - STAT Date/s: + - Cannot blackout more that 1 year at Once. - @@ -540,11 +547,11 @@ Recurring STAT - Step 2 (continued): Confirm Recurring Event DatesStep 2 (continued): Confirm Recurring Event Dates - Step 2 (continued): Confirm Recurring STAT DatesStep 2 (continued): Confirm Recurring STAT Dates @@ -618,15 +625,15 @@ Recurring Event - Step 3 (optional): Event Notes. - - + + Step 2 (optional): Event Notes. - + - + Filter Exams - + Filter Appointments - Search + Search - + - Filters: + Filters: - Start Date: + Start Date: - End Date: + End Date: - Exam Types: + Exam Types: - Office {{ msg }} {{ msg }} @@ -38,7 +38,7 @@ - Office # + Office #
Step 2 (optional): Event Notes. -