From e24489a92b27befdc2b7acae313260c499780059 Mon Sep 17 00:00:00 2001 From: Kuan Fan <31664961+kuanfandevops@users.noreply.github.com> Date: Thu, 6 Oct 2022 10:42:09 -0700 Subject: [PATCH] Tracking pull request to merge release-1.42.0 to master (#1091) * update to 1.42.0 * update workflow and pipeline to use 1.42.0 * feat: allow editable comments for model year report discussion (#1093) * feat: allow editable comments for model year report discussion * chore: cleanup and rearrange file structure * chore: linting * feat: zeva-267 - edit credit application comments (#1094) * feat: zeva-267 - edit credit application comments * small change * remove immer Co-authored-by: Alex Zorkin <47334977+AlexZorkin@users.noreply.github.com> * run backend test (#1099) * comment out test * fix: added filter that removes inactive users from email subscribed list (#1095) * fix: zeva-445 - highlight new models on submit (#1098) * fix: adds directorAction into condition for displaying 'return to analyst' button (#1101) * fix: version fix for enums (#1107) * fix: Zeva 443 - 21 error on old submissions (#1106) * fix: adds exclude to has_been_awarded filter to remove any records that have a later timestamp than the current submission * chore: linting * fix: adds more to raw sql query for 11 errors so that icbc data uploaded after the sales submission would be excluded when calculating warnings (ie it will show appropriate 11 errors) * Feat: Supplementary index links - ZEVA-444 (#1103) * fix: reworked index link logic on model year report table * chore: logic cleanup, linting * use python 3.9 (#1113) * add dev deployment * update workflow depencency * update apiVersion * remove sschema spy * chore: adds vehicleListSerializer so that vehicle list is returned faster, uses serializer method fields to return just necessary data rather than calling other serializers that return data that is not needed (#1112) * feat: adds condition for displaying checkboxes and button bars on model year report pages (#1109) * Fix: Model Year Report Tests for change log sales - ZEVA 432 (#1118) * fix: enum fixes on credit test * feat: model year reports testing harness fixed, assessment patch logic tested * chore: pretty ignore file * fix: preinitialize table render for validated-details view - ZEVA 460 (#1121) * fix: preinitialize table render for validated-details view * fix: logic fix to prevent re-rendering * Update frontend base image (#1117) * split dev deploy (#1127) * Frontend try 1.42.0 (#1130) Update the assemble file * update dev deploy name * feat: zeva-267 - add ability to delete comments (#1136) * remove dev deployment * -changes how action bar and signoff is conditionally rendered, so it now does not render if the report has been submitted, assessed, or reassessed (#1140) Co-authored-by: Alex Zorkin <47334977+AlexZorkin@users.noreply.github.com> Co-authored-by: tim738745 <98717409+tim738745@users.noreply.github.com> Co-authored-by: Emily <44536222+emi-hi@users.noreply.github.com> --- .circleci/config.yml | 29 ---- .github/workflows/build-new-python-image.yaml | 37 +++++ .github/workflows/build-release.yaml | 47 ++++++- .jenkins/.pipeline/lib/config.js | 2 +- .pipeline/lib/config.js | 2 +- .pipeline/lib/deploy.js | 3 +- backend/.s2i/bin/assemble | 106 +++++++++----- backend/Dockerfile | 2 +- .../api/models/sales_submission_content.py | 7 +- .../serializers/sales_submission_comment.py | 7 +- backend/api/serializers/vehicle.py | 40 ++++++ backend/api/services/send_email.py | 6 +- backend/api/tests/test_credit_requests.py | 11 +- .../tests/test_model_year_report_status.py | 44 ------ backend/api/tests/test_model_year_reports.py | 121 ++++++++++++++++ backend/api/viewsets/credit_request.py | 28 ++++ backend/api/viewsets/model_year_report.py | 28 +++- backend/api/viewsets/vehicle.py | 4 +- backend/gunicorn.cfg | 49 ------- backend/gunicorn.cfg.py | 2 + backend/requirements.txt | 2 +- frontend/.prettierignore | 6 + frontend/.s2i/bin/assemble | 130 +++++++++++++++--- frontend/.s2i/bin/run | 3 - frontend/Dockerfile-Openshift | 2 +- frontend/Dockerfile-Openshift-entrypoint.sh | 2 - frontend/package.json | 2 +- frontend/src/app/components/EditComment.js | 62 +++++++++ .../src/app/components/EditableCommentList.js | 84 +++++++++++ frontend/src/app/css/App.scss | 7 + frontend/src/app/css/Supplementary.scss | 46 ++++--- frontend/src/app/routes/Compliance.js | 1 + frontend/src/app/routes/CreditRequests.js | 4 +- .../src/compliance/AssessmentContainer.js | 40 ++++-- .../components/AssessmentDetailsPage.js | 59 +++++++- .../AssessmentEditableCommentInput.js | 95 +++++++++++++ .../AssessmentEditableCommentList.js | 54 ++++++++ .../ComplianceObligationDetailsPage.js | 93 +++++++------ .../ComplianceReportSummaryDetailsPage.js | 80 ++++++----- .../components/ComplianceReportsTable.js | 55 ++------ .../components/ConsumerSalesDetailsPage.js | 102 ++++++++------ .../SupplierInformationDetailsPage.js | 112 ++++++++------- .../components/CreditAgreementsFilter.js | 4 +- .../components/CreditAgreementsForm.js | 17 ++- .../credits/CreditRequestDetailsContainer.js | 52 +++++++ .../components/CreditRequestDetailsPage.js | 12 +- .../CreditRequestValidatedDetailsPage.js | 1 + .../src/credits/components/VINListTable.js | 7 +- .../components/SupplementaryDetailsPage.js | 8 +- .../vehicles/components/VehicleListTable.js | 4 +- openshift/templates/backend/backend-bc.yaml | 2 +- openshift/templates/backend/backend-dc.yaml | 14 +- openshift/templates/frontend/frontend-bc.yaml | 8 +- .../rabbitmq-secret-configmap-only.yaml | 2 +- 54 files changed, 1257 insertions(+), 490 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 .github/workflows/build-new-python-image.yaml mode change 100644 => 100755 backend/.s2i/bin/assemble delete mode 100644 backend/api/tests/test_model_year_report_status.py create mode 100644 backend/api/tests/test_model_year_reports.py delete mode 100644 backend/gunicorn.cfg create mode 100644 backend/gunicorn.cfg.py create mode 100644 frontend/.prettierignore mode change 100644 => 100755 frontend/.s2i/bin/assemble delete mode 100644 frontend/.s2i/bin/run delete mode 100644 frontend/Dockerfile-Openshift-entrypoint.sh create mode 100644 frontend/src/app/components/EditComment.js create mode 100644 frontend/src/app/components/EditableCommentList.js create mode 100644 frontend/src/compliance/components/AssessmentEditableCommentInput.js create mode 100644 frontend/src/compliance/components/AssessmentEditableCommentList.js diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 43b67a443..000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,29 +0,0 @@ -version: 2.1 - -orbs: - redhat-openshift: circleci/redhat-openshift@0.2.0 - -jobs: - build-in-Openshift: - executor: redhat-openshift/default - steps: - - checkout - - redhat-openshift/login-and-update-kubeconfig: - insecure-skip-tls-verify: true - openshift-platform-version: 3.x - server-address: $OC_SERVER_ADDRESS - token: $OC_TOKEN - - run: - name: test oc connection to path finder - command: oc projects - - run: - name: build frontend - command: | - oc start-build envoy --wait=true -n tbiwaq-tools - oc start-build frontend --wait=true -n tbiwaq-tools - oc start-build python-backend --wait=true -n tbiwaq-tools -workflows: - version: 2 - build-deploy: - jobs: - - build-in-Openshift \ No newline at end of file diff --git a/.github/workflows/build-new-python-image.yaml b/.github/workflows/build-new-python-image.yaml new file mode 100644 index 000000000..d2220811f --- /dev/null +++ b/.github/workflows/build-new-python-image.yaml @@ -0,0 +1,37 @@ +## For each release, the value of workflow name, branches, PR_NUMBER and RELEASE_NAME need to be adjusted accordingly +## Also change the .pipelin/lib/config.js version number +name: new-python-image + +on: + push: + branches: [ new-python39-image-1.42.0 ] + workflow_dispatch: + workflow_call: + +env: + ## The pull request number of the Tracking pull request to merge the release branch to main + PR_NUMBER: 1102 + RELEASE_NAME: new-python39-image-1.42.0 + +jobs: + + ## This is the CI job + build: + + name: Build ZEVA on Openshift + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + + ## it will checkout to /home/runner/work/zeva/zeva + - name: Check out repository + uses: actions/checkout@v2 + + # open it when zeva updated the python packages + - name: Run django tests + uses: kuanfandevops/django-test-action@zeva-django-test + with: + settings-dir-path: "backend/zeva" + requirements-file: "backend/requirements.txt" + managepy-dir: backend diff --git a/.github/workflows/build-release.yaml b/.github/workflows/build-release.yaml index e27eee2f7..65ed875ce 100644 --- a/.github/workflows/build-release.yaml +++ b/.github/workflows/build-release.yaml @@ -1,17 +1,17 @@ ## For each release, the value of workflow name, branches, PR_NUMBER and RELEASE_NAME need to be adjusted accordingly ## Also change the .pipelin/lib/config.js version number -name: CI/CD ZEVA release-1.41.0 +name: ZEVA v1.42.0 on: push: - branches: [ release-1.41.0 ] + branches: [ release-1.42.0 ] workflow_dispatch: workflow_call: env: ## The pull request number of the Tracking pull request to merge the release branch to main - PR_NUMBER: 1072 - RELEASE_NAME: release-1.41.0 + PR_NUMBER: 1091 + RELEASE_NAME: release-1.42.0 jobs: @@ -28,6 +28,14 @@ jobs: - name: Check out repository uses: actions/checkout@v2 + # open it when zeva updated the python packages + #- name: Run django tests + # uses: kuanfandevops/django-test-action@zeva-django-test + # with: + # settings-dir-path: "backend/zeva" + # requirements-file: "backend/requirements.txt" + # managepy-dir: backend + ## Log in to Openshift with a token of service account - name: Log in to Openshift uses: redhat-actions/oc-login@v1 @@ -44,12 +52,39 @@ jobs: npm install npm run build -- --pr=${{ env.PR_NUMBER }} --env=build + deploy-on-dev: + + name: Deploy ZEVA on Dev Environment + runs-on: ubuntu-latest + timeout-minutes: 60 + needs: build + + steps: + + - name: Check out repository + uses: actions/checkout@v2 + + - name: Log in to Openshift + uses: redhat-actions/oc-login@v1 + with: + openshift_server_url: ${{ secrets.OPENSHIFT_SERVER }} + openshift_token: ${{ secrets.OPENSHIFT_TOKEN }} + insecure_skip_tls_verify: true + namespace: ${{ secrets.OPENSHIFT_NAMESPACE_PLATE }}-tools + + #- name: Run BCDK deployment on ZEVA Dev environment + # run: | + # cd .pipeline + # echo "Deploying ZEVA ${{ env.RELEASE_NAME }} on Dev" + # npm install + # npm run deploy -- --pr=${{ env.PR_NUMBER }} --env=dev + deploy-on-test: name: Deploy ZEVA on Test Environment runs-on: ubuntu-latest timeout-minutes: 60 - needs: build + needs: deploy-on-dev steps: @@ -112,4 +147,4 @@ jobs: cd .pipeline echo "Deploying ZEVA ${{ env.RELEASE_NAME }} on Prod" npm install - npm run deploy -- --pr=${{ env.PR_NUMBER }} --env=prod \ No newline at end of file + npm run deploy -- --pr=${{ env.PR_NUMBER }} --env=prod diff --git a/.jenkins/.pipeline/lib/config.js b/.jenkins/.pipeline/lib/config.js index 15dfcbb36..4bee555ce 100644 --- a/.jenkins/.pipeline/lib/config.js +++ b/.jenkins/.pipeline/lib/config.js @@ -1,7 +1,7 @@ 'use strict'; const options= require('@bcgov/pipeline-cli').Util.parseArguments() const changeId = options.pr //aka pull-request -const version = '1.0.0' +const version = '1.42.0' const name = 'jenkins' const ocpName = 'apps.silver.devops' diff --git a/.pipeline/lib/config.js b/.pipeline/lib/config.js index 9340aa0b5..b0febdc47 100644 --- a/.pipeline/lib/config.js +++ b/.pipeline/lib/config.js @@ -1,7 +1,7 @@ 'use strict'; const options= require('@bcgov/pipeline-cli').Util.parseArguments() const changeId = options.pr //aka pull-request -const version = '1.41.0' +const version = '1.42.0' const name = 'zeva' const ocpName = 'apps.silver.devops' diff --git a/.pipeline/lib/deploy.js b/.pipeline/lib/deploy.js index 6ec09f087..29e4368cf 100755 --- a/.pipeline/lib/deploy.js +++ b/.pipeline/lib/deploy.js @@ -148,6 +148,7 @@ module.exports = settings => { })) //deploy schemaspy + /* if(phase === 'dev') { objects = objects.concat(oc.processDeploymentTemplate(`${templatesLocalBaseUrl}/templates/schemaspy/schemaspy-dc.yaml`, { 'param': { @@ -161,7 +162,7 @@ module.exports = settings => { 'OCP_NAME': phases[phase].ocpName } })) - } + }*/ /** //deploy rabbitmq, use docker image directly diff --git a/backend/.s2i/bin/assemble b/backend/.s2i/bin/assemble old mode 100644 new mode 100755 index 0767891df..1b547cbc4 --- a/backend/.s2i/bin/assemble +++ b/backend/.s2i/bin/assemble @@ -8,58 +8,104 @@ function should_collectstatic() { is_django_installed && [[ -z "$DISABLE_COLLECTSTATIC" ]] } -# Install pipenv to the separate virtualenv to isolate it +function virtualenv_bin() { + # New versions of Python (>3.6) should use venv module + # from stdlib instead of virtualenv package + python3.9 -m venv $1 +} + +# Install pipenv or micropipenv to the separate virtualenv to isolate it # from system Python packages and packages in the main # virtualenv. Executable is simlinked into ~/.local/bin # to be accessible. This approach is inspired by pipsi # (pip script installer). -function install_pipenv() { - echo "---> Installing pipenv packaging tool ..." - VENV_DIR=$HOME/.local/venvs/pipenv - virtualenv $VENV_DIR - $VENV_DIR/bin/pip --isolated install -U pipenv +function install_tool() { + echo "---> Installing $1 packaging tool ..." + VENV_DIR=$HOME/.local/venvs/$1 + virtualenv_bin "$VENV_DIR" + # First, try to install the tool without --isolated which means that if you + # have your own PyPI mirror, it will take it from there. If this try fails, try it + # again with --isolated which ignores external pip settings (env vars, config file) + # and installs the tool from PyPI (needs internet connetion). + # $1$2 combines package name with [extras] or version specifier if is defined as $2``` + if ! $VENV_DIR/bin/pip install -U $1$2; then + echo "WARNING: Installation of $1 failed, trying again from official PyPI with pip --isolated install" + $VENV_DIR/bin/pip install --isolated -U $1$2 # Combines package name with [extras] or version specifier if is defined as $2``` + fi mkdir -p $HOME/.local/bin - ln -s $VENV_DIR/bin/pipenv $HOME/.local/bin/pipenv + ln -s $VENV_DIR/bin/$1 $HOME/.local/bin/$1 } set -e +# First of all, check that we don't have disallowed combination of ENVs +if [[ ! -z "$ENABLE_PIPENV" && ! -z "$ENABLE_MICROPIPENV" ]]; then + echo "ERROR: Pipenv and micropipenv cannot be enabled at the same time!" + # podman/buildah does not relay this exit code but it will be fixed hopefuly + # https://github.com/containers/buildah/issues/2305 + exit 3 +fi + shopt -s dotglob echo "---> Installing application source ..." -mv /tmp/src/* ./ +mv /tmp/src/* "$HOME" -if [[ ! -z "$UPGRADE_PIP_TO_LATEST" || ! -z "$ENABLE_PIPENV" ]]; then - echo "---> Upgrading pip to latest version ..." - pip install -U pip setuptools wheel +# set permissions for any installed artifacts +fix-permissions /opt/app-root -P + + +if [[ ! -z "$UPGRADE_PIP_TO_LATEST" ]]; then + echo "---> Upgrading pip, setuptools and wheel to latest version ..." + if ! pip install -U pip setuptools wheel; then + echo "WARNING: Installation of the latest pip, setuptools and wheel failed, trying again from official PyPI with pip --isolated install" + pip install --isolated -U pip setuptools wheel + fi fi if [[ ! -z "$ENABLE_PIPENV" ]]; then - install_pipenv + if [[ ! -z "$PIN_PIPENV_VERSION" ]]; then + # Add == as a prefix to pipenv version, if defined + PIN_PIPENV_VERSION="==$PIN_PIPENV_VERSION" + fi + install_tool "pipenv" "$PIN_PIPENV_VERSION" echo "---> Installing dependencies via pipenv ..." if [[ -f Pipfile ]]; then pipenv install --deploy elif [[ -f requirements.txt ]]; then pipenv install -r requirements.txt fi - pipenv check + # pipenv check +elif [[ ! -z "$ENABLE_MICROPIPENV" ]]; then + install_tool "micropipenv" "[toml]" + echo "---> Installing dependencies via micropipenv ..." + # micropipenv detects Pipfile.lock and requirements.txt in this order + micropipenv install --deploy elif [[ -f requirements.txt ]]; then - echo "---> Installing dependencies ..." - pip install -i https://$ARTIFACTORY_USER:$ARTIFACTORY_PASSWORD@artifacts.developer.gov.bc.ca/artifactory/api/pypi/pypi-remote/simple --upgrade pip - pip install -i https://$ARTIFACTORY_USER:$ARTIFACTORY_PASSWORD@artifacts.developer.gov.bc.ca/artifactory/api/pypi/pypi-remote/simple -r requirements.txt -elif [[ -f setup.py ]]; then - echo "---> Installing application ..." - python setup.py develop + if [[ -z "${ARTIFACTORY_USER}" ]]; then + echo "---> Installing dependencies from external repo ..." + pip install -r requirements.txt + else + echo "---> Installing dependencies from artifactory ..." + pip install -i https://$ARTIFACTORY_USER:$ARTIFACTORY_PASSWORD@artifacts.developer.gov.bc.ca/artifactory/api/pypi/pypi-remote/simple -r requirements.txt + fi fi - +if [[ -f setup.py && -z "$DISABLE_SETUP_PY_PROCESSING" ]]; then + echo "---> Installing application ..." + pip install . +fi if should_collectstatic; then ( echo "---> Collecting Django static files ..." - APP_HOME=${APP_HOME:-.} - # Look for 'manage.py' in the directory specified by APP_HOME, or the current directory - manage_file=$APP_HOME/manage.py + APP_HOME=$(readlink -f "${APP_HOME:-.}") + # Change the working directory to APP_HOME + PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}" + cd "$APP_HOME" + + # Look for 'manage.py' in the current directory + manage_file=./manage.py if [[ ! -f "$manage_file" ]]; then echo "WARNING: seems that you're using Django, but we could not find a 'manage.py' file." @@ -75,20 +121,8 @@ if should_collectstatic; then fi python $manage_file collectstatic --noinput - ) fi -echo "---> current folder is " -pwd - -# Run unit tests in build stage with the code below -# echo "--> running Django unit tests" - -# python $manage_file test - # set permissions for any installed artifacts -fix-permissions /opt/app-root -echo "---> current folder2 is " -pwd -ls -lrt +fix-permissions /opt/app-root -P \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile index a937e1ddd..1343bdc97 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.6 +FROM python:3.9 ENV PYTHONUNBUFFERED=1 diff --git a/backend/api/models/sales_submission_content.py b/backend/api/models/sales_submission_content.py index 00d8ec528..398eae4da 100644 --- a/backend/api/models/sales_submission_content.py +++ b/backend/api/models/sales_submission_content.py @@ -84,7 +84,10 @@ def vehicle(self): @property def icbc_verification(self): - q = 'select * from icbc_registration_data where vin=\'{}\' limit 1'.format(self.xls_vin) + q = 'select * from icbc_registration_data join icbc_upload_date on \ + icbc_upload_date.id = icbc_upload_date_id where \ + vin=\'{}\' and upload_date < \'{}\' limit 1'.format( + self.xls_vin, self.update_timestamp) registration = IcbcRegistrationData.objects.raw(q) if registration: return registration[0] @@ -113,6 +116,8 @@ def is_already_awarded(self): submission_id=self.submission_id ).filter( vin=self.xls_vin + ).exclude( + create_timestamp__gte=self.update_timestamp ).first() if has_been_awarded: diff --git a/backend/api/serializers/sales_submission_comment.py b/backend/api/serializers/sales_submission_comment.py index cd53f9529..727740659 100644 --- a/backend/api/serializers/sales_submission_comment.py +++ b/backend/api/serializers/sales_submission_comment.py @@ -22,10 +22,15 @@ def get_create_user(self, obj): serializer = MemberSerializer(user, read_only=True) return serializer.data + def update(self, instance, validated_data): + instance.comment = validated_data.get("comment") + instance.save() + return instance + class Meta: model = SalesSubmissionComment fields = ( - 'id', 'comment', 'create_timestamp', 'create_user','to_govt' + 'id', 'comment', 'create_timestamp', 'create_user','to_govt', 'update_timestamp' ) read_only_fields = ( 'id', diff --git a/backend/api/serializers/vehicle.py b/backend/api/serializers/vehicle.py index c2b4aa774..b258f1af0 100644 --- a/backend/api/serializers/vehicle.py +++ b/backend/api/serializers/vehicle.py @@ -5,6 +5,7 @@ from api.models.model_year import ModelYear from api.models.credit_class import CreditClass +from api.models.organization import Organization from api.models.vehicle import Vehicle from api.models.vehicle_attachment import VehicleAttachment from api.models.vehicle_change_history import VehicleChangeHistory @@ -150,6 +151,45 @@ class Meta: fields = ('create_timestamp', 'create_user', 'validation_status') +class VehicleListSerializer( + ModelSerializer, EnumSupportSerializerMixin +): + organization = SerializerMethodField() + validation_status = EnumField(VehicleDefinitionStatuses, read_only=True) + credit_value = SerializerMethodField() + credit_class = SerializerMethodField() + model_year = SerializerMethodField() + vehicle_zev_type = SerializerMethodField() + + def get_organization(self, obj): + organization = Organization.objects.get(id=obj.organization_id) + name = organization.name + short_name = organization.short_name + return {'name': name, 'short_name': short_name} + + def get_credit_value(self, instance): + return instance.get_credit_value() + + def get_credit_class(self, instance): + return instance.get_credit_class() + + def get_model_year(self, obj): + model_year = ModelYear.objects.get(id=obj.model_year_id) + return model_year.name + + def get_vehicle_zev_type(self, obj): + zev_type = ZevType.objects.filter(id=obj.vehicle_zev_type_id).first() + return zev_type.vehicle_zev_code + + class Meta: + model = Vehicle + fields = ('id', 'organization', 'validation_status', + 'credit_value', 'credit_class', + 'model_year', 'model_name', 'make', + 'range', 'vehicle_zev_type', 'is_active' + ) + + class VehicleSerializer( ModelSerializer, EnumSupportSerializerMixin ): diff --git a/backend/api/services/send_email.py b/backend/api/services/send_email.py index a2a3976b7..e7dfd192a 100644 --- a/backend/api/services/send_email.py +++ b/backend/api/services/send_email.py @@ -271,8 +271,10 @@ def notifications_zev_model(request: object, validation_status: str): def subscribed_users(notifications: list, request: object, request_type: str, email_type: str): user_email = None try: - subscribed_users = NotificationSubscription.objects.values_list('user_profile_id', flat=True).filter(notification__id__in=notifications) - + subscribed_users = NotificationSubscription.objects.values_list('user_profile_id', flat=True).filter( + notification__id__in=notifications).filter( + user_profile__is_active=True + ) if subscribed_users: govt_org = Organization.objects.filter(is_government=True).first() if request_type == 'credit_transfer': diff --git a/backend/api/tests/test_credit_requests.py b/backend/api/tests/test_credit_requests.py index dd73154ac..cde6d57a0 100644 --- a/backend/api/tests/test_credit_requests.py +++ b/backend/api/tests/test_credit_requests.py @@ -7,6 +7,7 @@ from ..models.sales_submission import SalesSubmission from ..models.vehicle import Vehicle from ..models.vin_statuses import VINStatuses +from ..models.sales_submission_statuses import SalesSubmissionStatuses class TestSales(BaseTestCase): @@ -45,7 +46,7 @@ def test_validate_validation_status(self): sub = SalesSubmission.objects.create( organization=self.users['RTAN_BCEID'].organization, submission_sequence=1, - validation_status='DRAFT' + validation_status=SalesSubmissionStatuses.NEW ) request = { @@ -55,17 +56,15 @@ def test_validate_validation_status(self): # try changing from status NEW to VALIDATED, this should fail # ie it should throw a Validation Error self.assertRaises( - ValidationError, sub.validate_validation_status( - 'VALIDATED', request - ) + ValidationError, sub.validate_validation_status, SalesSubmissionStatuses.VALIDATED, request ) - sub.validation_status = 'RECOMMEND_APPROVAL' + sub.validation_status = SalesSubmissionStatuses.RECOMMEND_APPROVAL sub.save() # try changing from status RECOMMEND_APPROVAL to DELETED, this should # fail # ie it should throw a Validation Error self.assertRaises( - ValidationError, sub.validate_validation_status('DELETED', request) + ValidationError, sub.validate_validation_status, SalesSubmissionStatuses.DELETED, request ) diff --git a/backend/api/tests/test_model_year_report_status.py b/backend/api/tests/test_model_year_report_status.py deleted file mode 100644 index f79249796..000000000 --- a/backend/api/tests/test_model_year_report_status.py +++ /dev/null @@ -1,44 +0,0 @@ -# from django.utils.datetime_safe import datetime -# from rest_framework.serializers import ValidationError - -# from .base_test_case import BaseTestCase -# from ..models.model_year_report import ModelYearReport -# from ..models.supplemental_report import SupplementalReport -# from ..models.model_year_report_statuses import ModelYearReportStatuses -# from ..models.organization import Organization -# from ..models.model_year import ModelYear - - -# class TestModelYearReports(BaseTestCase): -# def setUp(self): -# super().setUp() - -# org1 = self.users['EMHILLIE_BCEID'].organization -# gov = self.users['RTAN'].organization - -# model_year_report = ModelYearReport.objects.create( -# organization=org1, -# create_user='EMHILLIE_BCEID', -# validation_status=ModelYearReportStatuses.ASSESSED, -# organization_name=Organization.objects.get('BMW Canada Inc.'), -# supplier_class='M', -# model_year=ModelYear.objects.get('2021'), -# credit_reduction_selection='A' -# ) -# supplementary_report = SupplementalReport.objects.create( -# create_user='EMHILLIE_BCEID', -# validation_status=ModelYearReportStatuses.DRAFT, -# ) -# reassessment_report = SupplementalReport.objects.create( -# create_user='RTAN', -# validation_status=ModelYearReportStatuses.DRAFT, -# ) - -# def test_status(self): -# response = self.clients['EMHILLIE_BCEID'].get("/api/compliance/reports") -# self.assertEqual(response.status_code, 200) -# result = response.data -# print('(((((((((())))))))))') -# print(result) -# print('(((((((((())))))))))') -# # self.assertEqual(len(result), 1) diff --git a/backend/api/tests/test_model_year_reports.py b/backend/api/tests/test_model_year_reports.py new file mode 100644 index 000000000..95d5b030a --- /dev/null +++ b/backend/api/tests/test_model_year_reports.py @@ -0,0 +1,121 @@ +from email import header +import json +from django.utils.datetime_safe import datetime +from rest_framework.serializers import ValidationError + +from .base_test_case import BaseTestCase +from ..models.model_year_report import ModelYearReport +from ..models.supplemental_report import SupplementalReport +from ..models.model_year_report_statuses import ModelYearReportStatuses +from ..models.model_year_report_assessment import ModelYearReportAssessment +from ..models.model_year_report_assessment_descriptions import ModelYearReportAssessmentDescriptions +from ..models.model_year_report_ldv_sales import ModelYearReportLDVSales +from ..models.organization import Organization +from ..models.model_year import ModelYear + + +class TestModelYearReports(BaseTestCase): + def setUp(self): + super().setUp() + + org1 = self.users['EMHILLIE_BCEID'].organization + gov = self.users['RTAN'].organization + + model_year_report = ModelYearReport.objects.create( + organization=org1, + create_user='EMHILLIE_BCEID', + validation_status=ModelYearReportStatuses.ASSESSED, + organization_name=Organization.objects.get(name='BMW Canada Inc.'), + supplier_class='M', + model_year=ModelYear.objects.get(effective_date='2021-01-01'), + credit_reduction_selection='A' + ) + supplementary_report = SupplementalReport.objects.create( + model_year_report=model_year_report, + create_user='EMHILLIE_BCEID', + status=ModelYearReportStatuses.DRAFT, + ) + model_year_report_assessment_description = ModelYearReportAssessmentDescriptions.objects.create( + description='test', + display_order=1 + ) + model_year_report_assessment = ModelYearReportAssessment.objects.create( + model_year_report=model_year_report, + model_year_report_assessment_description=model_year_report_assessment_description, + penalty=20.00 + ) + reassessment_report = SupplementalReport.objects.create( + model_year_report=model_year_report, + supplemental_id=supplementary_report.id, + create_user='RTAN', + status=ModelYearReportStatuses.DRAFT, + ) + + def test_status(self): + response = self.clients['EMHILLIE_BCEID'].get("/api/compliance/reports") + self.assertEqual(response.status_code, 200) + result = response.data + self.assertEqual(len(result), 1) + + + def test_assessment_patch_response(self): + makes = ["TESLATRUCK", "TESLA", "TEST"] + sales = {"2020":25} + data = json.dumps({"makes":makes, "sales":sales}) + response = self.clients['RTAN'].patch("/api/compliance/reports/1/assessment_patch", data=data, content_type='application/json') + self.assertEqual(response.status_code, 200) + response = self.clients['RTAN'].patch("/api/compliance/reports/999/assessment_patch", data=data, content_type='application/json') + self.assertEqual(response.status_code, 404) + + + def test_assessment_patch_logic(self): + makes = ["TESLATRUCK", "TESLA", "TEST"] + sales = {"2020":25} + model_year = ModelYear.objects.filter(id=2).first() + model_year_report = ModelYearReport.objects.filter(id=1).first() + data = json.dumps({"makes":makes, "sales":sales}) + + modelYearReportLDVSales1 = ModelYearReportLDVSales.objects.create( + model_year=model_year, + ldv_sales=10, + model_year_report=model_year_report + ) + + response = self.clients['RTAN'].patch("/api/compliance/reports/1/assessment_patch", data=data, content_type='application/json') + + sales_records = ModelYearReportLDVSales.objects.filter( + model_year_id=model_year.id, + model_year_report=model_year_report) + + # Check that second record is created + self.assertEqual(sales_records.count(), 2) + + data = json.dumps({"makes":makes, "sales":{"2020":10}}) + response = self.clients['RTAN'].patch("/api/compliance/reports/1/assessment_patch", data=data, content_type='application/json') + + sales_records = ModelYearReportLDVSales.objects.filter( + model_year_id=model_year.id, + model_year_report=model_year_report) + + # Check for proper deletion of first record + self.assertEqual(sales_records.count(), 1) + + modelYearReportLDVSales2 = ModelYearReportLDVSales.objects.create( + model_year=model_year, + ldv_sales=10, + from_gov=True, + model_year_report=model_year_report + ) + + data = json.dumps({"makes":makes, "sales":{"2020":50}}) + response = self.clients['RTAN'].patch("/api/compliance/reports/1/assessment_patch", data=data, content_type='application/json') + + sales_records = ModelYearReportLDVSales.objects.filter( + model_year_id=model_year.id, + model_year_report=model_year_report) + + sales_record = ModelYearReportLDVSales.objects.filter(id=3).first() + + # check that second record is updated, and no new record created + self.assertEqual(sales_records.count(), 2) + self.assertEqual(sales_record.ldv_sales, 50) diff --git a/backend/api/viewsets/credit_request.py b/backend/api/viewsets/credit_request.py index b734bacd5..9a18ee2dc 100644 --- a/backend/api/viewsets/credit_request.py +++ b/backend/api/viewsets/credit_request.py @@ -8,10 +8,13 @@ from django.db.models import Subquery, Count, Q from django.db.models.expressions import RawSQL from django.http import HttpResponse, HttpResponseForbidden +from api.models.sales_submission_comment import SalesSubmissionComment +from api.serializers.sales_submission_comment import SalesSubmissionCommentSerializer from rest_framework import mixins, viewsets from rest_framework.decorators import action from rest_framework.response import Response +from rest_framework import status from api.models.icbc_registration_data import IcbcRegistrationData from api.models.record_of_sale import RecordOfSale @@ -478,3 +481,28 @@ def download_details(self, request, pk): ) ) return response + + @action(detail=True, methods=["PATCH"]) + def update_comment(self, request, pk): + comment_text = request.data.get("comment") + username = request.user.username + comment = SalesSubmissionComment.objects.get( + id=pk + ) + if username == comment.create_user: + serializer = SalesSubmissionCommentSerializer(comment, data={'comment': comment_text}, partial=True) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(serializer.data) + return Response(status=status.HTTP_403_FORBIDDEN) + + @action(detail=True, methods=["PATCH"]) + def delete_comment(self, request, pk): + username = request.user.username + comment = SalesSubmissionComment.objects.get( + id=pk + ) + if username == comment.create_user: + comment.delete() + return Response(status=status.HTTP_200_OK) + return Response(status=status.HTTP_403_FORBIDDEN) diff --git a/backend/api/viewsets/model_year_report.py b/backend/api/viewsets/model_year_report.py index a73ef3cd4..9d256e634 100644 --- a/backend/api/viewsets/model_year_report.py +++ b/backend/api/viewsets/model_year_report.py @@ -1,7 +1,7 @@ import uuid from django.db.models import Q from django.shortcuts import get_object_or_404 -from django.http import HttpResponse +from django.http import HttpResponse, HttpResponseForbidden from rest_framework.response import Response from rest_framework import mixins, viewsets from rest_framework.decorators import action @@ -562,6 +562,32 @@ def comment_save(self, request, pk): return Response(serializer.data) + + @action(detail=True, methods=['patch']) + def comment_patch(self, request, pk): + # only government users can edit comments + if not request.user.is_government: + return HttpResponseForbidden() + + id = request.data.get('id') + comment = request.data.get('comment') + + modelYearReportAssessmentComment = get_object_or_404(ModelYearReportAssessmentComment, pk=id) + + # only the original commenter can edit a comment + if request.user.username != modelYearReportAssessmentComment.create_user: + return HttpResponseForbidden() + + modelYearReportAssessmentComment.comment = comment + modelYearReportAssessmentComment.save() + + report = get_object_or_404(ModelYearReport, pk=pk) + + serializer = ModelYearReportSerializer(report, context={'request': request}) + + return Response(serializer.data) + + @action(detail=True, methods=['get']) def assessment(self, request, pk): report = get_object_or_404(ModelYearReport, pk=pk) diff --git a/backend/api/viewsets/vehicle.py b/backend/api/viewsets/vehicle.py index 1b8a3ae1a..b46942a3d 100644 --- a/backend/api/viewsets/vehicle.py +++ b/backend/api/viewsets/vehicle.py @@ -15,7 +15,8 @@ from api.serializers.vehicle import ModelYearSerializer, \ VehicleZevTypeSerializer, VehicleClassSerializer, \ VehicleSaveSerializer, VehicleSerializer, \ - VehicleStatusChangeSerializer, VehicleIsActiveChangeSerializer + VehicleStatusChangeSerializer, VehicleIsActiveChangeSerializer, \ + VehicleListSerializer from api.services.minio import minio_put_object from auditable.views import AuditableMixin from api.models.vehicle import VehicleDefinitionStatuses @@ -30,6 +31,7 @@ class VehicleViewSet( serializer_classes = { 'default': VehicleSerializer, + 'list': VehicleListSerializer, 'state_change': VehicleStatusChangeSerializer, 'is_active_change': VehicleIsActiveChangeSerializer, 'create': VehicleSaveSerializer, diff --git a/backend/gunicorn.cfg b/backend/gunicorn.cfg deleted file mode 100644 index 32cbf5339..000000000 --- a/backend/gunicorn.cfg +++ /dev/null @@ -1,49 +0,0 @@ -# Gunicorn configuration file. - -# Worker processes -# -# workers - The number of worker processes that this server -# should keep alive for handling requests. -# -# A positive integer generally in the 2-4 x $(NUM_CORES) -# range. You'll want to vary this a bit to find the best -# for your particular application's work load. -# -# worker_class - The type of workers to use. The default -# sync class should handle most 'normal' types of work -# loads. You'll want to read -# http://docs.gunicorn.org/en/latest/design.html#choosing-a-worker-type -# for information on when you might want to choose one -# of the other worker classes. -# -# A string referring to a Python path to a subclass of -# gunicorn.workers.base.Worker. The default provided values -# can be seen at -# http://docs.gunicorn.org/en/latest/settings.html#worker-class -# -# worker_connections - For the eventlet and gevent worker classes -# this limits the maximum number of simultaneous clients that -# a single process can handle. -# -# A positive integer generally set to around 1000. -# -# timeout - If a worker does not notify the master process in this -# number of seconds it is killed and a new worker is spawned -# to replace it. -# -# Generally set to thirty seconds. Only set this noticeably -# higher if you're sure of the repercussions for sync workers. -# For the non sync workers it just means that the worker -# process is still communicating and is not tied to the length -# of time required to handle a single request. -# -# keepalive - The number of seconds to wait for the next request -# on a Keep-Alive HTTP connection. -# -# A positive integer. Generally set in the 1-5 seconds range. -# - -workers = 8 -timeout = 1800 -graceful_timeout = 1800 -keepalive = 5 diff --git a/backend/gunicorn.cfg.py b/backend/gunicorn.cfg.py new file mode 100644 index 000000000..ce8d7e77b --- /dev/null +++ b/backend/gunicorn.cfg.py @@ -0,0 +1,2 @@ +bind = "0.0.0.0:8080" +workers = 2 \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt index 14be5a8ae..85ee9c349 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -12,7 +12,7 @@ cryptography==3.4.7 Django==3.1.12 django-celery-beat==1.5.0 django-cors-headers==3.2.1 -django-enumfields==2.0.0 +django-enumfields==2.1.1 django-filter==2.4.0 django-timezone-field==4.0 djangorestframework==3.12.4 diff --git a/frontend/.prettierignore b/frontend/.prettierignore new file mode 100644 index 000000000..10f9d5722 --- /dev/null +++ b/frontend/.prettierignore @@ -0,0 +1,6 @@ +# Ignore artifacts: +build +coverage + +# Ignore all HTML files: +*.html \ No newline at end of file diff --git a/frontend/.s2i/bin/assemble b/frontend/.s2i/bin/assemble old mode 100644 new mode 100755 index 33bcf8c8a..19faefa24 --- a/frontend/.s2i/bin/assemble +++ b/frontend/.s2i/bin/assemble @@ -1,36 +1,130 @@ +sh-5.1$ cat assemble #!/bin/bash # Prevent running assemble in builders different than official STI image. -# The official nodejs:0.10-onbuild already run npm install and use different +# The official nodejs:8-onbuild already run npm install and use different # application folder. -# if /user/src/app directory exists, quit this script with status 0 [ -d "/usr/src/app" ] && exit 0 -# from "help set", it says "-e Exit immediately if a command exits with a non-zero status." set -e -# there are options which modify the behavior of bash, they can be set or unset using shopt -# -s means If optnames are specified, set those options. If no optnames are specified, list all options that are currently set. -# -u can Unset optnames. -shopt -s dotglob +# FIXME: Linking of global modules is disabled for now as it causes npm failures +# under RHEL7 +# Global modules good to have +# npmgl=$(grep "^\s*[^#\s]" ../etc/npm_global_module_list | sort -u) +# Available global modules; only match top-level npm packages +#global_modules=$(npm ls -g 2> /dev/null | perl -ne 'print "$1\n" if /^\S+\s(\S+)\@[\d\.-]+/' | sort -u) +# List all modules in common +#module_list=$(/usr/bin/comm -12 <(echo "${global_modules}") | tr '\n' ' ') +# Link the modules +#npm link $module_list + +safeLogging () { + if [[ $1 =~ http[s]?://.*@.*$ ]]; then + echo $1 | sed 's/^.*@/redacted@/' + else + echo $1 + fi +} +shopt -s dotglob +if [ -d /tmp/artifacts ] && [ "$(ls /tmp/artifacts/ 2>/dev/null)" ]; then + echo "---> Restoring previous build artifacts ..." + mv -T --verbose /tmp/artifacts/node_modules "${HOME}/node_modules" +fi -# tfrs/frontend/* were copied at /tmp/src, copy /tmp/src/* into /opt/app-root/src echo "---> Installing application source ..." -cp -r /tmp/src/* ./ && rm -rf /tmp/src/* +mv /tmp/src/* ./ + +# Fix source directory permissions +fix-permissions ./ + +if [ ! -z $HTTP_PROXY ]; then + echo "---> Setting npm http proxy to" $(safeLogging $HTTP_PROXY) + npm config set proxy $HTTP_PROXY +fi + +if [ ! -z $http_proxy ]; then + echo "---> Setting npm http proxy to" $(safeLogging $http_proxy) + npm config set proxy $http_proxy +fi + +if [ ! -z $HTTPS_PROXY ]; then + echo "---> Setting npm https proxy to" $(safeLogging $HTTPS_PROXY) + npm config set https-proxy $HTTPS_PROXY +fi + +if [ ! -z $https_proxy ]; then + echo "---> Setting npm https proxy to" $(safeLogging $https_proxy) + npm config set https-proxy $https_proxy +fi + +# Change the npm registry mirror if provided +if [ -n "$NPM_MIRROR" ]; then + npm config set registry $NPM_MIRROR +fi + +# Set the DEV_MODE to false by default. +if [ -z "$DEV_MODE" ]; then + export DEV_MODE=false +fi + +# If NODE_ENV is not set by the user, then NODE_ENV is determined by whether +# the container is run in development mode. +if [ -z "$NODE_ENV" ]; then + if [ "$DEV_MODE" == true ]; then + export NODE_ENV=development + else + export NODE_ENV=production + fi +fi + +if [ "$NODE_ENV" != "production" ]; then + + echo "---> Building your Node application from source" + npm install + +else + + echo "---> Have to set DEV_MODE and NODE_ENV to empty otherwise the deployment can not be started" + echo "---> It'll have error like can not resolve source-map-loader..." + export DEV_MODE="" + export NODE_ENV="" + + if [[ -z "${ARTIFACTORY_USER}" ]]; then + echo "---> Installing all dependencies from external repo" + else + echo "---> Installing all dependencies from Artifactory" + npm config set registry https://artifacts.developer.gov.bc.ca/artifactory/api/npm/npm-remote/ + curl -u $ARTIFACTORY_USER:$ARTIFACTORY_PASSWORD https://artifacts.developer.gov.bc.ca/artifactory/api/npm/auth >> ~/.npmrc + fi + + echo "---> Installing all dependencies" + NODE_ENV=development npm install -echo "---> Building your Node application from source" + #do not fail when there is no build script + echo "---> Building in production mode" + npm run build --if-present -# pull node packages from artifactory -npm cache clean --force -npm config set registry https://artifacts.developer.gov.bc.ca/artifactory/api/npm/npm-remote/ -curl -u $ARTIFACTORY_USER:$ARTIFACTORY_PASSWORD https://artifacts.developer.gov.bc.ca/artifactory/api/npm/auth >> ~/.npmrc + echo "---> Pruning the development dependencies" + npm prune -# -d means --loglevel info -npm install -d + NPM_TMP=$(npm config get tmp) + if ! mountpoint $NPM_TMP; then + echo "---> Cleaning the $NPM_TMP/npm-*" + rm -rf $NPM_TMP/npm-* + fi -# run webpack -npm run dist + # Clear the npm's cache and tmp directories only if they are not a docker volumes + NPM_CACHE=$(npm config get cache) + if ! mountpoint $NPM_CACHE; then + echo "---> Cleaning the npm cache $NPM_CACHE" + #As of npm@5 even the 'npm cache clean --force' does not fully remove the cache directory + # instead of $NPM_CACHE* use $NPM_CACHE/*. + # We do not want to delete .npmrc file. + rm -rf "${NPM_CACHE:?}/" + fi +fi # Fix source directory permissions fix-permissions ./ \ No newline at end of file diff --git a/frontend/.s2i/bin/run b/frontend/.s2i/bin/run deleted file mode 100644 index b1b930ce3..000000000 --- a/frontend/.s2i/bin/run +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -npm run production \ No newline at end of file diff --git a/frontend/Dockerfile-Openshift b/frontend/Dockerfile-Openshift index 59a2319a8..9aa32a355 100644 --- a/frontend/Dockerfile-Openshift +++ b/frontend/Dockerfile-Openshift @@ -11,4 +11,4 @@ CMD npm run start chmod +x /usr/local/bin/caddy2 # COPY ./Caddyfile /etc/caddy/Caddyfile # RUN chmod +x ./Dockerfile-Openshift-entrypoint.sh -# CMD ["./Dockerfile-Openshift-entrypoint.sh"] +# CMD ["./Dockerfile-Openshift-entrypoint.sh"] \ No newline at end of file diff --git a/frontend/Dockerfile-Openshift-entrypoint.sh b/frontend/Dockerfile-Openshift-entrypoint.sh deleted file mode 100644 index 113f61a5a..000000000 --- a/frontend/Dockerfile-Openshift-entrypoint.sh +++ /dev/null @@ -1,2 +0,0 @@ -nohup caddy2 run --config ./Caddyfile & -npm run start diff --git a/frontend/package.json b/frontend/package.json index 313b5cb17..2f96bcbf7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "zeva-frontend", - "version": "1.41.0", + "version": "1.42.0", "private": true, "dependencies": { "@fortawesome/fontawesome-free": "^5.13.0", diff --git a/frontend/src/app/components/EditComment.js b/frontend/src/app/components/EditComment.js new file mode 100644 index 000000000..41063bbdc --- /dev/null +++ b/frontend/src/app/components/EditComment.js @@ -0,0 +1,62 @@ +import React, { useState, useEffect } from 'react'; +import ReactQuill from 'react-quill'; + +const EditComment = ({ + commentId, + comment, + handleSave, + handleCancel, + handleDelete +}) => { + const [value, setValue] = useState(); + + useEffect(() => { + setValue(comment); + }, [comment]); + + const handleChange = (editedComment) => { + if (editedComment) { + setValue(editedComment); + } + }; + + return ( + <> + + + + + ); +}; + +export default EditComment; diff --git a/frontend/src/app/components/EditableCommentList.js b/frontend/src/app/components/EditableCommentList.js new file mode 100644 index 000000000..ab42ec55e --- /dev/null +++ b/frontend/src/app/components/EditableCommentList.js @@ -0,0 +1,84 @@ +import React, { useState } from 'react'; +import EditComment from './EditComment'; +import moment from 'moment-timezone'; +import parse from 'html-react-parser'; + +const EditableCommentList = ({ + comments, + user, + handleCommentEdit, + handleCommentDelete +}) => { + const [commentIdsBeingEdited, setCommentIdsBeingEdited] = useState([]); + + const handleEditClick = (commentId) => { + setCommentIdsBeingEdited((prev) => { + return [...prev, commentId]; + }); + }; + + const handleSave = (commentId, commentText) => { + handleCommentEdit(commentId, commentText); + handleCancel(commentId); + }; + + const handleCancel = (commentId) => { + setCommentIdsBeingEdited((prev) => { + return prev.filter((id) => { + return id !== commentId; + }); + }); + }; + + const handleDelete = (commentId) => { + handleCommentDelete(commentId); + handleCancel(commentId); + }; + + const commentElements = []; + for (const comment of comments) { + const commentId = comment.id; + const beingEdited = commentIdsBeingEdited.includes(commentId); + const userEditable = comment.createUser.id == user.id; + if (beingEdited) { + commentElements.push( +
+ +
+ ); + } else { + commentElements.push( +
+ {'Comments - '} + {userEditable && ( + + )} + {comment.createUser.displayName},{' '} + {moment(comment.updateTimestamp).format('YYYY-MM-DD h[:]mm a')} :{' '} + {parse(comment.comment)} +
+
+ ); + } + } + return ( +
+ {commentElements} +
+ ); +}; + +export default EditableCommentList; diff --git a/frontend/src/app/css/App.scss b/frontend/src/app/css/App.scss index 3668faeb4..8e53e46bf 100644 --- a/frontend/src/app/css/App.scss +++ b/frontend/src/app/css/App.scss @@ -360,3 +360,10 @@ p { .ql-editor { height: auto; } + +button { + &.inline-edit { + color: #003366; + border: none; + } +} diff --git a/frontend/src/app/css/Supplementary.scss b/frontend/src/app/css/Supplementary.scss index e579007e4..638a62add 100644 --- a/frontend/src/app/css/Supplementary.scss +++ b/frontend/src/app/css/Supplementary.scss @@ -1,4 +1,3 @@ - #supplementary { .supplementary-form { border: 1px solid $border-grey; @@ -14,6 +13,9 @@ background-color: #e9ecef; opacity: 1; border: 1px solid #ced4da; + &.highlight { + background-color: $background-warning; + } } } } @@ -22,7 +24,7 @@ border: 1px solid $border-grey; padding: 0.5rem; } - + .content { background-color: $default-background-grey; } @@ -34,11 +36,11 @@ } .no-border { - border: none; + border: none; } .credit-selection { - input[type=radio] { + input[type='radio'] { margin-left: 3px; } @@ -59,7 +61,7 @@ table { width: 100%; border: 1px solid $border-grey; - + td { padding: 0 1rem 0 1rem; line-height: 35px; @@ -96,45 +98,51 @@ .supplementary-report-tabs { .nav-item { padding: 1rem 0; - - &.DRAFT, &.UNSAVED, &.SAVED { + + &.DRAFT, + &.UNSAVED, + &.SAVED { a { border-bottom-color: $background-warning; } - + &.active span { border-bottom-color: $primary-yellow; } } - - &.CONFIRMED, &.SUBMITTED, &.RECOMMENDED, &.RETURNED { + + &.CONFIRMED, + &.SUBMITTED, + &.RECOMMENDED, + &.RETURNED { a { - border-bottom-color: $background-light-blue; + border-bottom-color: $background-light-blue; } - + &.active span { border-bottom-color: $default-text-blue; } } - + &.ASSESSED { a { border-bottom-color: $background-light-green; } - + &.active span { border-bottom-color: $alert-success; } } - - a, span { + + a, + span { border-bottom: 1rem transparent solid; color: $default-text-black; display: block; } - + .disabled { - opacity: .3; + opacity: 0.3; } } } @@ -142,4 +150,4 @@ .highlight.form-control { background-color: $background-warning !important; } -} \ No newline at end of file +} diff --git a/frontend/src/app/routes/Compliance.js b/frontend/src/app/routes/Compliance.js index ec8088267..262b695e9 100644 --- a/frontend/src/app/routes/Compliance.js +++ b/frontend/src/app/routes/Compliance.js @@ -22,6 +22,7 @@ const COMPLIANCE = { REPORT_SUMMARY_CONFIRMATION: `${API_BASE_PATH}/reports/:id/submission_confirmation`, REPORT_SUBMISSION: `${API_BASE_PATH}/reports/submission`, ASSESSMENT_COMMENT_SAVE: `${API_BASE_PATH}/reports/:id/comment_save`, + ASSESSMENT_COMMENT_PATCH: `${API_BASE_PATH}/reports/:id/comment_patch`, YEARS: `${API_BASE_PATH}/reports/years`, MAKES: `${API_BASE_PATH}/reports/:id/makes`, SUPPLEMENTAL_CREATE: `${API_BASE_PATH}/reports/:id/supplemental_save`, diff --git a/frontend/src/app/routes/CreditRequests.js b/frontend/src/app/routes/CreditRequests.js index 5f9e4f44a..c8fa932e4 100644 --- a/frontend/src/app/routes/CreditRequests.js +++ b/frontend/src/app/routes/CreditRequests.js @@ -16,7 +16,9 @@ const CREDIT_REQUESTS = { UPLOAD: `${API_BASE_PATH}/upload`, VALIDATE: `${API_BASE_PATH}/:id/validate`, VALIDATED: `${API_BASE_PATH}/:id/validated`, - VALIDATED_DETAILS: `${API_BASE_PATH}/:id/validated-details` + VALIDATED_DETAILS: `${API_BASE_PATH}/:id/validated-details`, + UPDATE_COMMENT: `${API_BASE_PATH}/:id/update_comment`, + DELETE_COMMENT: `${API_BASE_PATH}/:id/delete_comment` }; export default CREDIT_REQUESTS; diff --git a/frontend/src/compliance/AssessmentContainer.js b/frontend/src/compliance/AssessmentContainer.js index b502adce8..9dc5347d8 100644 --- a/frontend/src/compliance/AssessmentContainer.js +++ b/frontend/src/compliance/AssessmentContainer.js @@ -85,6 +85,32 @@ const AssessmentContainer = (props) => { history.replace(ROUTES_COMPLIANCE.REPORT_ASSESSMENT.replace(':id', id)); }); }; + + const handleAddBceidComment = () => { + const comment = { comment: bceidComment, director: false }; + axios + .post( + ROUTES_COMPLIANCE.ASSESSMENT_COMMENT_SAVE.replace(':id', id), + comment + ) + .then(() => { + history.push(ROUTES_COMPLIANCE.REPORTS); + history.replace(ROUTES_COMPLIANCE.REPORT_ASSESSMENT.replace(':id', id)); + }); + }; + + const handleEditComment = (comment) => { + axios + .patch( + ROUTES_COMPLIANCE.ASSESSMENT_COMMENT_PATCH.replace(':id', id), + comment + ) + .then(() => { + history.push(ROUTES_COMPLIANCE.REPORTS); + history.replace(ROUTES_COMPLIANCE.REPORT_ASSESSMENT.replace(':id', id)); + }); + }; + const refreshDetails = () => { if (id) { axios @@ -485,19 +511,6 @@ const AssessmentContainer = (props) => { }); }; - const handleAddBceidComment = () => { - const comment = { comment: bceidComment, director: false }; - axios - .post( - ROUTES_COMPLIANCE.ASSESSMENT_COMMENT_SAVE.replace(':id', id), - comment - ) - .then(() => { - history.push(ROUTES_COMPLIANCE.REPORTS); - history.replace(ROUTES_COMPLIANCE.REPORT_ASSESSMENT.replace(':id', id)); - }); - }; - return ( <> { handleAddIdirComment={handleAddIdirComment} handleCommentChangeBceid={handleCommentChangeBceid} handleCommentChangeIdir={handleCommentChangeIdir} + handleEditComment={handleEditComment} handleSubmit={handleSubmit} id={id} loading={loading} diff --git a/frontend/src/compliance/components/AssessmentDetailsPage.js b/frontend/src/compliance/components/AssessmentDetailsPage.js index 741339ff8..a431836a3 100644 --- a/frontend/src/compliance/components/AssessmentDetailsPage.js +++ b/frontend/src/compliance/components/AssessmentDetailsPage.js @@ -15,9 +15,10 @@ import formatNumeric from '../../app/utilities/formatNumeric'; import ComplianceObligationReductionOffsetTable from './ComplianceObligationReductionOffsetTable'; import ComplianceObligationTableCreditsIssued from './ComplianceObligationTableCreditsIssued'; import CommentInput from '../../app/components/CommentInput'; -import DisplayComment from '../../app/components/DisplayComment'; import ROUTES_SUPPLEMENTARY from '../../app/routes/SupplementaryReport'; import ComplianceHistory from './ComplianceHistory'; +import AssessmentEditableCommentList from './AssessmentEditableCommentList'; +import AssessmentEditableCommentInput from './AssessmentEditableCommentInput'; const AssessmentDetailsPage = (props) => { const { @@ -28,6 +29,7 @@ const AssessmentDetailsPage = (props) => { handleAddIdirComment, handleCommentChangeBceid, handleCommentChangeIdir, + handleEditComment, loading, makes, reportYear, @@ -55,6 +57,9 @@ const AssessmentDetailsPage = (props) => { const [showModal, setShowModal] = useState(false); const [showModalAssess, setShowModalAssess] = useState(false); + const [editableComment, setEditableComment] = useState(null); + const [editText, setEditText] = useState(''); + const formattedPenalty = formatNumeric( details.assessment.assessmentPenalty, 0 @@ -178,6 +183,33 @@ const AssessmentDetailsPage = (props) => { } }; + const editComment = (comment) => { + const text = comment.comment; + setEditableComment(comment); + setEditText(text); + handleCommentChangeBceid(text); + handleCommentChangeIdir(text); + }; + + const updateEditableCommentText = (text) => { + setEditText(text); + handleCommentChangeBceid(text); + handleCommentChangeIdir(text); + }; + + const saveEditableComment = () => { + let comment = editableComment; + comment.comment = editText; + handleEditComment(comment); + }; + + const cancelEditableComment = () => { + setEditText(''); + setEditableComment(null); + handleCommentChangeIdir(''); + handleCommentChangeBceid(''); + }; + return (
{ (Object.keys(details.changelog.makesAdditions) || details.changelog.ldvChanges > 0) && ( <> -

Assessment Adjustments

+

Internal Record of Assessment

The analyst made the following adjustments: {details.changelog.makesAdditions && ( @@ -253,18 +285,30 @@ const AssessmentDetailsPage = (props) => { {details.idirComment && details.idirComment.length > 0 && user.isGovernment && ( - + )} {statuses.assessment.status !== 'ASSESSED' && ( - )}
@@ -663,6 +707,7 @@ AssessmentDetailsPage.propTypes = { handleAddIdirComment: PropTypes.func.isRequired, handleCommentChangeBceid: PropTypes.func.isRequired, handleCommentChangeIdir: PropTypes.func.isRequired, + handleEditComment: PropTypes.func.isRequired, radioDescriptions: PropTypes.arrayOf(PropTypes.shape()).isRequired, setDetails: PropTypes.func.isRequired, classAReductions: PropTypes.arrayOf(PropTypes.shape()).isRequired, diff --git a/frontend/src/compliance/components/AssessmentEditableCommentInput.js b/frontend/src/compliance/components/AssessmentEditableCommentInput.js new file mode 100644 index 000000000..5d70e336f --- /dev/null +++ b/frontend/src/compliance/components/AssessmentEditableCommentInput.js @@ -0,0 +1,95 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ReactQuill from 'react-quill'; +import ReactTooltip from 'react-tooltip'; + +const AssessmentEditableCommentInput = (props) => { + const { + handleAddComment, + handleCommentChange, + saveEditableComment, + cancelEditableComment, + editing, + title, + buttonText, + disable, + buttonDisable, + tooltip, + value + } = props; + return ( +
+ + + {!disable && buttonText && ( + <> + {tooltip !== '' && buttonDisable && } + + + + {editing && ( + + )} + + + )} +
+ ); +}; + +AssessmentEditableCommentInput.defaultProps = { + buttonText: null, + editing: false, + disable: false, + handleAddComment: () => {}, + tooltip: '' +}; +AssessmentEditableCommentInput.propTypes = { + handleAddComment: PropTypes.func, + handleCommentChange: PropTypes.func.isRequired, + saveEditableComment: PropTypes.func.isRequired, + cancelEditableComment: PropTypes.func.isRequired, + buttonText: PropTypes.string, + title: PropTypes.string.isRequired, + disable: PropTypes.bool, + tooltip: PropTypes.string +}; +export default AssessmentEditableCommentInput; diff --git a/frontend/src/compliance/components/AssessmentEditableCommentList.js b/frontend/src/compliance/components/AssessmentEditableCommentList.js new file mode 100644 index 000000000..f375a3acf --- /dev/null +++ b/frontend/src/compliance/components/AssessmentEditableCommentList.js @@ -0,0 +1,54 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import moment from 'moment-timezone'; +import parse from 'html-react-parser'; +import { Link } from 'react-router-dom'; + +const AssessmentEditableCommentList = (props) => { + const { commentArray, editComment, user } = props; + return ( +
+ + {commentArray && + commentArray.map((each) => { + const comment = + typeof each.comment === 'string' ? each : each.comment; + const userEditable = user.id == each.createUser.id; + return ( +
+ {'Comments '} + {!userEditable && } + {userEditable && ( + + )} + {each.createUser.displayName},{' '} + {moment(each.createTimestamp).format('YYYY-MM-DD h[:]mm a')} :{' '} + {parse(each.comment)} +
+
+ ); + })} +
+
+ ); +}; + +AssessmentEditableCommentList.defaultProps = { + commentArray: [] +}; +AssessmentEditableCommentList.propTypes = { + commentArray: PropTypes.arrayOf(PropTypes.shape()), + editComment: PropTypes.func.isRequired +}; +export default AssessmentEditableCommentList; diff --git a/frontend/src/compliance/components/ComplianceObligationDetailsPage.js b/frontend/src/compliance/components/ComplianceObligationDetailsPage.js index 455771025..6fd487223 100644 --- a/frontend/src/compliance/components/ComplianceObligationDetailsPage.js +++ b/frontend/src/compliance/components/ComplianceObligationDetailsPage.js @@ -169,49 +169,58 @@ const ComplianceObligationDetailsPage = (props) => { user={user} />
- -
-
-
- -
+
- - + + )} {modal} ); diff --git a/frontend/src/compliance/components/ComplianceReportSummaryDetailsPage.js b/frontend/src/compliance/components/ComplianceReportSummaryDetailsPage.js index b60e84b2c..f3c0f13f3 100644 --- a/frontend/src/compliance/components/ComplianceReportSummaryDetailsPage.js +++ b/frontend/src/compliance/components/ComplianceReportSummaryDetailsPage.js @@ -229,46 +229,54 @@ const ComplianceReportSummaryDetailsPage = (props) => { + {['SUBMITTED', 'ASSESSED', 'REASSESSED'].indexOf( + confirmationStatuses.reportSummary.status + ) == -1 && ( + <> +
+
+ +
+
-
-
- -
-
- -
-
-
- -
+
- - + + )} {modal} ); diff --git a/frontend/src/compliance/components/ComplianceReportsTable.js b/frontend/src/compliance/components/ComplianceReportsTable.js index 0983f68f3..c2b94d695 100644 --- a/frontend/src/compliance/components/ComplianceReportsTable.js +++ b/frontend/src/compliance/components/ComplianceReportsTable.js @@ -175,63 +175,36 @@ const ComplianceReportsTable = (props) => { supplementalStatus } = row.original; - if (supplementalStatus === 'REASSESSED' && supplementalId) { - history.push( - ROUTES_SUPPLEMENTARY.SUPPLEMENTARY_DETAILS.replace( - /:id/g, - id - ).replace(/:supplementaryId/g, supplementalId) - ); - } else if ( + if ( supplementalStatus === 'SUPPLEMENTARY SUBMITTED' && - supplementalId - ) { - if (user.isGovernment) { - history.push({ - pathname: - ROUTES_SUPPLEMENTARY.SUPPLEMENTARY_DETAILS.replace( - /:id/g, - id - ).replace(/:supplementaryId/g, supplementalId), - search: '?reassessment=Y' - }); - } else { - history.push( - ROUTES_SUPPLEMENTARY.SUPPLEMENTARY_DETAILS.replace( - /:id/g, - id - ).replace(/:supplementaryId/g, supplementalId) - ); - } - } else if ( - ['REASSESSMENT DRAFT', 'REASSESSMENT RECOMMENDED'].indexOf( - supplementalStatus - ) >= 0 && supplementalId && user.isGovernment ) { - history.push( - ROUTES_SUPPLEMENTARY.SUPPLEMENTARY_DETAILS.replace( + history.push({ + pathname: ROUTES_SUPPLEMENTARY.SUPPLEMENTARY_DETAILS.replace( /:id/g, id - ).replace(/:supplementaryId/g, supplementalId) - ); - } else if (validationStatus === 'ASSESSED') { - history.push( - ROUTES_COMPLIANCE.REPORT_ASSESSMENT.replace(/:id/g, id) - ); - } else if (supplementalId) { + ).replace(/:supplementaryId/g, supplementalId), + search: '?reassessment=Y' + }); + return; + } + + if (supplementalId) { + // Shows latest supplementary report if one exists history.push( ROUTES_SUPPLEMENTARY.SUPPLEMENTARY_DETAILS.replace( /:id/g, id ).replace(/:supplementaryId/g, supplementalId) ); - } else if (user.isGovernment) { + } else if (validationStatus === 'ASSESSED') { + // If there is no supplementary report then we default to the first myr history.push( ROUTES_COMPLIANCE.REPORT_ASSESSMENT.replace(/:id/g, id) ); } else { + // Default show the supplier information page history.push( ROUTES_COMPLIANCE.REPORT_SUPPLIER_INFORMATION.replace( /:id/g, diff --git a/frontend/src/compliance/components/ConsumerSalesDetailsPage.js b/frontend/src/compliance/components/ConsumerSalesDetailsPage.js index 24b116cc0..701d39835 100644 --- a/frontend/src/compliance/components/ConsumerSalesDetailsPage.js +++ b/frontend/src/compliance/components/ConsumerSalesDetailsPage.js @@ -166,53 +166,65 @@ const ConsumerSalesDetailsPage = (props) => { -
-
- -
-
- -
-
-
- -
-
- + +
+
+
+ +
+
+
+ + )} {modal} ); diff --git a/frontend/src/compliance/components/SupplierInformationDetailsPage.js b/frontend/src/compliance/components/SupplierInformationDetailsPage.js index 2c5da789b..ad0d12fc9 100644 --- a/frontend/src/compliance/components/SupplierInformationDetailsPage.js +++ b/frontend/src/compliance/components/SupplierInformationDetailsPage.js @@ -298,57 +298,71 @@ const SupplierInformationDetailsPage = (props) => { - -
-
- -
-
- -
-
-
- -
-
- + +
+
+
+ +
+
+
+ + )} + {modal} ); diff --git a/frontend/src/creditagreements/components/CreditAgreementsFilter.js b/frontend/src/creditagreements/components/CreditAgreementsFilter.js index 982b47a1b..68a1c5f09 100644 --- a/frontend/src/creditagreements/components/CreditAgreementsFilter.js +++ b/frontend/src/creditagreements/components/CreditAgreementsFilter.js @@ -8,8 +8,8 @@ import handleFilterChange from '../../app/utilities/handleFilterChange'; const CreditAgreementsFilter = (props) => { const { user, items, handleClear, filtered, setFiltered } = props; - const isDirector = user.isGovernment - && user.roles.some(r => r.roleCode === 'Director'); + const isDirector = + user.isGovernment && user.roles.some((r) => r.roleCode === 'Director'); const handleChange = (event) => { setFiltered(handleFilterChange(event, filtered)); }; diff --git a/frontend/src/creditagreements/components/CreditAgreementsForm.js b/frontend/src/creditagreements/components/CreditAgreementsForm.js index d3d6539a6..adb471595 100644 --- a/frontend/src/creditagreements/components/CreditAgreementsForm.js +++ b/frontend/src/creditagreements/components/CreditAgreementsForm.js @@ -54,7 +54,10 @@ const CreditAgreementsForm = (props) => { agreementDetails.transactionType === 'Reassessment Reduction') ) { for (const modelYearReport of modelYearReports) { - if (modelYearReport.organizationId == parseInt(agreementDetails.vehicleSupplier)) { + if ( + modelYearReport.organizationId == + parseInt(agreementDetails.vehicleSupplier) + ) { supplierReports.push(modelYearReport); } } @@ -174,7 +177,11 @@ const CreditAgreementsForm = (props) => { dropdownData={suppliers} dropdownName="Vehicle Supplier" handleInputChange={(event) => { - handleChangeDetails(event.target.value, 'vehicleSupplier', true); + handleChangeDetails( + event.target.value, + 'vehicleSupplier', + true + ); }} fieldName="vehicleSupplier" accessor={(supplier) => supplier.id} @@ -189,7 +196,11 @@ const CreditAgreementsForm = (props) => { dropdownData={transactionTypes} dropdownName="Transaction Type" handleInputChange={(event) => { - handleChangeDetails(event.target.value, 'transactionType', true); + handleChangeDetails( + event.target.value, + 'transactionType', + true + ); }} fieldName="transactionType" accessor={(transactionType) => transactionType.name} diff --git a/frontend/src/credits/CreditRequestDetailsContainer.js b/frontend/src/credits/CreditRequestDetailsContainer.js index dce8df566..39a311d3e 100644 --- a/frontend/src/credits/CreditRequestDetailsContainer.js +++ b/frontend/src/credits/CreditRequestDetailsContainer.js @@ -84,6 +84,56 @@ const CreditRequestDetailsContainer = (props) => { }); }; + const handleInternalCommentEdit = (commentId, commentText) => { + axios + .patch(ROUTES_CREDIT_REQUESTS.UPDATE_COMMENT.replace(':id', commentId), { + comment: commentText + }) + .then((response) => { + const updatedComment = response.data; + setSubmission((prev) => { + const commentIndex = prev.salesSubmissionComment.findIndex( + (comment) => { + return comment.id === updatedComment.id; + } + ); + const comment = prev.salesSubmissionComment[commentIndex]; + const commentCopy = { ...comment }; + commentCopy.comment = updatedComment.comment; + commentCopy.updateTimestamp = updatedComment.updateTimestamp; + + const comments = prev.salesSubmissionComment; + const commentsCopy = [...comments]; + commentsCopy[commentIndex] = commentCopy; + + const submissionCopy = { ...prev }; + submissionCopy.salesSubmissionComment = commentsCopy; + return submissionCopy; + }); + }); + }; + + const handleInternalCommentDelete = (commentId) => { + axios + .patch(ROUTES_CREDIT_REQUESTS.DELETE_COMMENT.replace(':id', commentId)) + .then(() => { + setSubmission((prev) => { + const commentIndex = prev.salesSubmissionComment.findIndex( + (comment) => { + return comment.id === commentId; + } + ); + const comments = prev.salesSubmissionComment; + const commentsCopy = [...comments]; + commentsCopy.splice(commentIndex, 1); + + const submissionCopy = { ...prev }; + submissionCopy.salesSubmissionComment = commentsCopy; + return submissionCopy; + }); + }); + }; + if (loading) { return ; } @@ -101,6 +151,8 @@ const CreditRequestDetailsContainer = (props) => { validatedOnly={validatedOnly} handleCheckboxClick={handleCheckboxClick} issueAsMY={issueAsMY} + handleInternalCommentEdit={handleInternalCommentEdit} + handleInternalCommentDelete={handleInternalCommentDelete} /> ]; }; diff --git a/frontend/src/credits/components/CreditRequestDetailsPage.js b/frontend/src/credits/components/CreditRequestDetailsPage.js index c5ff1ddac..447c0ff7d 100644 --- a/frontend/src/credits/components/CreditRequestDetailsPage.js +++ b/frontend/src/credits/components/CreditRequestDetailsPage.js @@ -22,6 +22,7 @@ import getFileSize from '../../app/utilities/getFileSize'; import DisplayComment from '../../app/components/DisplayComment'; import formatNumeric from '../../app/utilities/formatNumeric'; import DownloadAllSubmissionContentButton from './DownloadAllSubmissionContentButton'; +import EditableCommentList from '../../app/components/EditableCommentList'; const CreditRequestDetailsPage = (props) => { const { @@ -30,7 +31,9 @@ const CreditRequestDetailsPage = (props) => { submission, user, issueAsMY, - handleCheckboxClick + handleCheckboxClick, + handleInternalCommentEdit, + handleInternalCommentDelete } = props; const { id } = useParams(); @@ -351,8 +354,11 @@ const CreditRequestDetailsPage = (props) => { user.isGovernment && ( <> Internal Comments - )} diff --git a/frontend/src/credits/components/CreditRequestValidatedDetailsPage.js b/frontend/src/credits/components/CreditRequestValidatedDetailsPage.js index 9925749c3..855e491e4 100644 --- a/frontend/src/credits/components/CreditRequestValidatedDetailsPage.js +++ b/frontend/src/credits/components/CreditRequestValidatedDetailsPage.js @@ -201,6 +201,7 @@ const CreditRequestValidatedDetailsPage = (props) => { setPages={setPages} setReactTable={setReactTable} user={user} + preInitialize={true} /> diff --git a/frontend/src/credits/components/VINListTable.js b/frontend/src/credits/components/VINListTable.js index 20b77f2eb..ed84b34e9 100644 --- a/frontend/src/credits/components/VINListTable.js +++ b/frontend/src/credits/components/VINListTable.js @@ -25,7 +25,8 @@ const VINListTable = (props) => { reasons, refreshContent, setFiltered, - setReactTable + setReactTable, + preInitialize } = props; const [tableInitialized, setTableInitialized] = useState(false); @@ -347,7 +348,9 @@ const VINListTable = (props) => { // onFetchData is called on component load (and on changes afterword) // which we want to avoid, so this tableInitialized // variable cancels out the first call to this method - if (!tableInitialized) { + if(!tableInitialized && preInitialize) { + setTableInitialized(true); + } else if (!tableInitialized) { setTableInitialized(true); return; } diff --git a/frontend/src/supplementary/components/SupplementaryDetailsPage.js b/frontend/src/supplementary/components/SupplementaryDetailsPage.js index a2a43948d..07c9ee2cd 100644 --- a/frontend/src/supplementary/components/SupplementaryDetailsPage.js +++ b/frontend/src/supplementary/components/SupplementaryDetailsPage.js @@ -676,11 +676,14 @@ const SupplementaryDetailsPage = (props) => { (isEditable || ['SUBMITTED', 'RECOMMENDED'].indexOf(details.status) >= 0) && user.isGovernment && - ((currentStatus === 'SUBMITTED' && + (((currentStatus === 'SUBMITTED' || + currentStatus === 'RETURNED') && + analystAction && details && details.reassessment && !details.reassessment.isReassessment) || (currentStatus === 'RECOMMENDED' && + directorAction && details && details.reassessment && details.reassessment.isReassessment)) && ( @@ -695,7 +698,8 @@ const SupplementaryDetailsPage = (props) => { }} type="button" > - {currentStatus === 'SUBMITTED' + {currentStatus === 'SUBMITTED' || + currentStatus === 'RETURNED' ? 'Return to Vehicle Supplier' : 'Return to Analyst'} diff --git a/frontend/src/vehicles/components/VehicleListTable.js b/frontend/src/vehicles/components/VehicleListTable.js index 84a28a640..251dee899 100644 --- a/frontend/src/vehicles/components/VehicleListTable.js +++ b/frontend/src/vehicles/components/VehicleListTable.js @@ -54,7 +54,7 @@ const VehicleListTable = (props) => { width: 125 }, { - accessor: (row) => (row.modelYear ? row.modelYear.name : ''), + accessor: (row) => row.modelYear, className: 'text-center', Header: 'Model Year', id: 'col-my', @@ -80,7 +80,7 @@ const VehicleListTable = (props) => { width: 125 }, { - accessor: (row) => row.vehicleZevType.vehicleZevCode, + accessor: (row) => row.vehicleZevType, className: 'text-center', Header: 'ZEV Type', id: 'zev-type', diff --git a/openshift/templates/backend/backend-bc.yaml b/openshift/templates/backend/backend-bc.yaml index 6f145341d..db3f54b4e 100644 --- a/openshift/templates/backend/backend-bc.yaml +++ b/openshift/templates/backend/backend-bc.yaml @@ -98,7 +98,7 @@ objects: key: password from: kind: ImageStreamTag - name: python:3.6-1-134 + name: python-39:1-74 pullSecret: name: zeva-image-pull-secret forcePull: true diff --git a/openshift/templates/backend/backend-dc.yaml b/openshift/templates/backend/backend-dc.yaml index 6108eeac0..c038a7641 100644 --- a/openshift/templates/backend/backend-dc.yaml +++ b/openshift/templates/backend/backend-dc.yaml @@ -168,7 +168,7 @@ objects: - /bin/sh - '-c' - |- - sleep 90 + sleep 45 python ./manage.py migrate if [ $? -eq 0 ]; then python ./manage.py load_ops_data --directory ./api/fixtures/operational @@ -314,7 +314,7 @@ objects: name: ${NAME}-config${SUFFIX} key: rabbitmq_port - name: APP_CONFIG - value: /opt/app-root/src/gunicorn.cfg + value: /opt/app-root/src/gunicorn.cfg.py - name: ENV_NAME valueFrom: configMapKeyRef: @@ -362,19 +362,11 @@ objects: secretKeyRef: name: email-service key: SENDER_EMAIL - livenessProbe: - failureThreshold: 30 - tcpSocket: - port: 8080 - initialDelaySeconds: ${{HEALTH_CHECK_DELAY}} - periodSeconds: 15 - successThreshold: 1 - timeoutSeconds: 3 ports: - containerPort: 8080 protocol: TCP readinessProbe: - failureThreshold: 30 + failureThreshold: 90 tcpSocket: port: 8080 initialDelaySeconds: ${{HEALTH_CHECK_DELAY}} diff --git a/openshift/templates/frontend/frontend-bc.yaml b/openshift/templates/frontend/frontend-bc.yaml index b03b2c36d..a3374b8f9 100644 --- a/openshift/templates/frontend/frontend-bc.yaml +++ b/openshift/templates/frontend/frontend-bc.yaml @@ -58,10 +58,10 @@ objects: resources: limits: cpu: 2000m - memory: 2G + memory: 4Gi requests: - cpu: 500m - memory: 200M + cpu: 1000m + memory: 2Gi runPolicy: Serial source: git: @@ -73,7 +73,7 @@ objects: sourceStrategy: from: kind: ImageStreamTag - name: nodejs:14-1-28 + name: nodejs-16:1-59 namespace: e52f12-tools pullSecret: name: zeva-image-pull-secret diff --git a/openshift/templates/rabbitmq/rabbitmq-secret-configmap-only.yaml b/openshift/templates/rabbitmq/rabbitmq-secret-configmap-only.yaml index 22feae87f..e767730f3 100644 --- a/openshift/templates/rabbitmq/rabbitmq-secret-configmap-only.yaml +++ b/openshift/templates/rabbitmq/rabbitmq-secret-configmap-only.yaml @@ -1,4 +1,4 @@ -apiVersion: v1 +apiVersion: template.openshift.io/v1 kind: Template metadata: name: rabbitmq-cluster