From ff7200f936e8d8c0c551cf43d50da6b9ffffe522 Mon Sep 17 00:00:00 2001 From: David Heitzer Date: Tue, 18 Jun 2024 16:51:11 -0400 Subject: [PATCH 01/33] 1925 e2e test strategy update --- .circleci/config.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3fe20b50c1..55b3037da3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -232,6 +232,25 @@ jobs: name: Deploy docs to gh-pages branch command: npx gh-pages --dotfiles --dist ~/project/docs/_build/html + trigger-e2e-test-pipeline: + docker: + - image: cimg/base:2021.11 + resource_class: small + steps: + - run: + name: Ping another pipeline + command: | + CREATED_PIPELINE=$( + curl -v https://circleci.com/api/v2/project/gh/fecgov/fecfile-web-app/pipeline \ + --header "Content-Type: application/json" \ + --header "Accept: application/json" \ + --header "Circle-Token: $CIRCLE_TOKEN" \ + --data '{"branch":"'$CIRCLE_BRANCH'","parameters":{"is-triggered-e2e-test":true}}' \ + | jq -r '.id' + ) + echo "created pipeline" + echo $CREATED_PIPELINE + # Invoke jobs via workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows workflows: @@ -239,6 +258,10 @@ workflows: jobs: - test - dependency-check + - trigger-e2e-test-pipeline: + filters: + branches: + only: /develop|release\/sprint-[\.\d]+|main/ - deploy-job: requires: - test From 2f57a9b7b0bf3981e668037a5913e24b6f09123c Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Thu, 27 Jun 2024 08:09:20 -0400 Subject: [PATCH 02/33] WIP e2e changes --- django-backend/fecfiler/reports/models.py | 4 ++-- django-backend/fecfiler/reports/views.py | 11 +++-------- django-backend/fixtures/e2e-test-data.json | 4 ++-- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/django-backend/fecfiler/reports/models.py b/django-backend/fecfiler/reports/models.py index 672dd13687..ce37e3cca8 100644 --- a/django-backend/fecfiler/reports/models.py +++ b/django-backend/fecfiler/reports/models.py @@ -136,8 +136,8 @@ def amend(self): self.upload_submission = None self.save() - def delete(self): - if not self.can_delete(): + def delete(self, force_delete=False): + if not self.can_delete() and not force_delete: raise ValidationError("Cannot delete report", status.HTTP_400_BAD_REQUEST) if not self.form_24: """only delete transactions if the report is the source of the diff --git a/django-backend/fecfiler/reports/views.py b/django-backend/fecfiler/reports/views.py index 2f45c8992f..50a8bb86c9 100644 --- a/django-backend/fecfiler/reports/views.py +++ b/django-backend/fecfiler/reports/views.py @@ -139,15 +139,10 @@ def amend(self, request, pk): @action( detail=False, methods=["post"], - url_path="hard-delete-reports", + url_path="e2e-delete-all-reports", ) - def hard_delete_reports(self, request): - committee_id = request.data.get("committee_id") - if not committee_id: - return Response( - "No committee_id provided", status=status.HTTP_400_BAD_REQUEST - ) - + def e2e_delete_all_reports(self, request): + committee_id = "C99999999" reports = Report.objects.filter(committee_account__committee_id=committee_id) report_count = reports.count() transactions = Transaction.objects.filter( diff --git a/django-backend/fixtures/e2e-test-data.json b/django-backend/fixtures/e2e-test-data.json index d80df7d183..ac74422546 100644 --- a/django-backend/fixtures/e2e-test-data.json +++ b/django-backend/fixtures/e2e-test-data.json @@ -6,7 +6,7 @@ "password": "pbkdf2_sha256$260000$8lcKqWHCEOwOMqtzTV6oDr$/fvcNuEmdyOaEpKjISOJuwngn5enhl3uSaEDI7N+RNk=", "is_superuser": false, "email": "test@test.com", - "username": "C00601211test@test.com", + "username": "C99999999test@test.com", "first_name": "Test", "last_name": "Testson", "last_login": "2022-05-02T13:44:07.324Z", @@ -19,7 +19,7 @@ "model": "committee_accounts.CommitteeAccount", "pk": "c94c5d1a-9e73-464d-ad72-b73b5d8667a9", "fields": { - "committee_id": "C00601211", + "committee_id": "C99999999", "created": "2022-02-09T00:00:00.000Z", "updated": "2022-02-09T00:00:00.000Z" } From 34fa8039e2e9596a184b51705ffe6197caa67681 Mon Sep 17 00:00:00 2001 From: David Heitzer Date: Thu, 27 Jun 2024 15:45:38 -0400 Subject: [PATCH 03/33] 1925 added explanations in circleci config --- .circleci/config.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 55b3037da3..73dfbe4a75 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -232,6 +232,10 @@ jobs: name: Deploy docs to gh-pages branch command: npx gh-pages --dotfiles --dist ~/project/docs/_build/html + # This job kicks off the e2e-test pipeline in the fecfile-web-app project. + # It is used to run the e2e tests when the api is deployed to dev/stage/prod. + # It uses the is-triggered-e2e-test parameter which is used in the fecfile-web-app + # circleci config file to differentiate it from the nightly runs. trigger-e2e-test-pipeline: docker: - image: cimg/base:2021.11 @@ -258,6 +262,8 @@ workflows: jobs: - test - dependency-check + # This job is triggered whenever a commit is made to the dev/stage/prod branches. + # It kicks off the e2e-test pipeline in the fecfile-web-app project. - trigger-e2e-test-pipeline: filters: branches: From 683eb4c81aecf6640a9a76099dd394845ff860e4 Mon Sep 17 00:00:00 2001 From: Matt Travers Date: Thu, 27 Jun 2024 18:21:08 -0400 Subject: [PATCH 04/33] Update function to calculate loan payment to date --- ...9_update_calculate_loan_payment_to_date.py | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 django-backend/fecfiler/transactions/migrations/0009_update_calculate_loan_payment_to_date.py diff --git a/django-backend/fecfiler/transactions/migrations/0009_update_calculate_loan_payment_to_date.py b/django-backend/fecfiler/transactions/migrations/0009_update_calculate_loan_payment_to_date.py new file mode 100644 index 0000000000..7afcd56cb1 --- /dev/null +++ b/django-backend/fecfiler/transactions/migrations/0009_update_calculate_loan_payment_to_date.py @@ -0,0 +1,68 @@ +from django.db import migrations + + +def update_existing_rows(apps, schema_editor): + transaction = apps.get_model("transactions", "Transaction") + types = [ + 'LOAN_RECEIVED_FROM_INDIVIDUAL', + 'LOAN_RECEIVED_FROM BANK', + 'LOAN_BY_COMMITTEE', + ] + for row in transaction.objects.filter(transaction_type_identifier__in=types): + row.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "transactions", + "0008_transaction__calendar_ytd_per_election_office_and_more" + ), + ] + + operations = [ + migrations.RunSQL( + """ + CREATE OR REPLACE FUNCTION calculate_loan_payment_to_date( + txn RECORD, + sql_committee_id TEXT, + temp_table_name TEXT + ) + RETURNS VOID AS $$ + BEGIN + EXECUTE ' + CREATE TEMPORARY TABLE ' || temp_table_name || ' + ON COMMIT DROP AS + SELECT + id, + loan_key, + SUM(effective_amount) OVER (ORDER BY loan_key) AS new_sum + FROM transaction_view__' || sql_committee_id || ' + WHERE loan_key LIKE ( + SELECT + CASE + WHEN loan_id IS NULL THEN transaction_id + ELSE ( + SELECT transaction_id + FROM transactions_transaction + WHERE id = t.loan_id + ) + END + FROM transactions_transaction t + WHERE id = $1 + ) || ''%%''; -- Match the loan_key with a transaction_id prefix + + UPDATE transactions_transaction AS t + SET loan_payment_to_date = tt.new_sum + FROM ' || temp_table_name || ' AS tt + WHERE t.id = tt.id + AND tt.loan_key LIKE ''%%LOAN''; + ' + USING txn.id; + END; + $$ LANGUAGE plpgsql; + """ + ), + migrations.RunPython(update_existing_rows), + ] From 730842dbe5c73e2c92e6cbde6a7afa529bd2e88c Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Thu, 27 Jun 2024 18:27:12 -0400 Subject: [PATCH 05/33] Restores the report delete() method since that's no longer necessary --- django-backend/fecfiler/reports/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django-backend/fecfiler/reports/models.py b/django-backend/fecfiler/reports/models.py index ce37e3cca8..672dd13687 100644 --- a/django-backend/fecfiler/reports/models.py +++ b/django-backend/fecfiler/reports/models.py @@ -136,8 +136,8 @@ def amend(self): self.upload_submission = None self.save() - def delete(self, force_delete=False): - if not self.can_delete() and not force_delete: + def delete(self): + if not self.can_delete(): raise ValidationError("Cannot delete report", status.HTTP_400_BAD_REQUEST) if not self.form_24: """only delete transactions if the report is the source of the From f65603b6be16e30df1e9ffa53b514f82663c4f0b Mon Sep 17 00:00:00 2001 From: Sasha Dresden Date: Fri, 28 Jun 2024 15:09:48 -0400 Subject: [PATCH 06/33] Update gunicorn to 22.0.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9c2ad92bde..ec9e07d86f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ djangorestframework==3.14.0 git+https://github.com/fecgov/fecfile-validate@0f8b966b623fbca644aebb2054fe4829eb0e0a93#egg=fecfile_validate&subdirectory=fecfile_validate_python GitPython==3.1.42 github3.py==4.0.1 -gunicorn==20.1.0 +gunicorn==22.0.0 invoke==1.7.3 itypes==1.2.0 MarkupSafe==2.1.1 From 7f6192552966872950528174fb5801bd9cc1e848 Mon Sep 17 00:00:00 2001 From: Laura Beaufort Date: Fri, 28 Jun 2024 16:23:58 -0400 Subject: [PATCH 07/33] CORS: Allow all *.fecfile.fec.gov origins --- django-backend/fecfiler/settings/base.py | 4 ++-- django-backend/fecfiler/settings/local.py | 1 + manifests/manifest-dev-api.yml | 1 - manifests/manifest-prod-api.yml | 1 - manifests/manifest-stage-api.yml | 1 - 5 files changed, 3 insertions(+), 5 deletions(-) diff --git a/django-backend/fecfiler/settings/base.py b/django-backend/fecfiler/settings/base.py index 494b16beaf..0f17fbbfef 100644 --- a/django-backend/fecfiler/settings/base.py +++ b/django-backend/fecfiler/settings/base.py @@ -110,8 +110,8 @@ }, ] -CORS_ALLOWED_ORIGINS = [ - env.get_credential("CORS_ALLOWED_ORIGINS", "http://localhost:4200") +CORS_ALLOWED_ORIGIN_REGEXES = [ + r"^https://(.*?).fecfile\.fec\.gov$" ] CORS_ALLOW_HEADERS = default_headers + ("enctype", "token") diff --git a/django-backend/fecfiler/settings/local.py b/django-backend/fecfiler/settings/local.py index 1fdab84a40..e8151bce5e 100644 --- a/django-backend/fecfiler/settings/local.py +++ b/django-backend/fecfiler/settings/local.py @@ -2,6 +2,7 @@ # These settings are for local development only. +CORS_ALLOWED_ORIGIN_REGEXES.append("http://localhost:4200") # NOSONAR # noqa: F405 try: from .local import * # NOSONAR # noqa: F401, F403 diff --git a/manifests/manifest-dev-api.yml b/manifests/manifest-dev-api.yml index 1acf3edb41..a5f5afcc2d 100644 --- a/manifests/manifest-dev-api.yml +++ b/manifests/manifest-dev-api.yml @@ -17,7 +17,6 @@ applications: env: DISABLE_COLLECTSTATIC: 1 DJANGO_SETTINGS_MODULE: fecfiler.settings.production - CORS_ALLOWED_ORIGINS: https://fecfile-web-app-dev.app.cloud.gov CSRF_TRUSTED_ORIGINS: https://fecfile-web-app-dev.app.cloud.gov FFAPI_COOKIE_DOMAIN: app.cloud.gov LOGIN_REDIRECT_CLIENT_URL: https://fecfile-web-app-dev.app.cloud.gov diff --git a/manifests/manifest-prod-api.yml b/manifests/manifest-prod-api.yml index 22ef8528f2..d997949d6e 100644 --- a/manifests/manifest-prod-api.yml +++ b/manifests/manifest-prod-api.yml @@ -17,7 +17,6 @@ applications: env: DISABLE_COLLECTSTATIC: 1 DJANGO_SETTINGS_MODULE: fecfiler.settings.production - CORS_ALLOWED_ORIGINS: https://fecfile-web-app-prod.app.cloud.gov CSRF_TRUSTED_ORIGINS: https://fecfile-web-app-prod.app.cloud.gov FFAPI_COOKIE_DOMAIN: app.cloud.gov LOGIN_REDIRECT_CLIENT_URL: https://fecfile-web-app-prod.app.cloud.gov diff --git a/manifests/manifest-stage-api.yml b/manifests/manifest-stage-api.yml index 960dd8e32b..976a47be26 100644 --- a/manifests/manifest-stage-api.yml +++ b/manifests/manifest-stage-api.yml @@ -17,7 +17,6 @@ applications: env: DISABLE_COLLECTSTATIC: 1 DJANGO_SETTINGS_MODULE: fecfiler.settings.production - CORS_ALLOWED_ORIGINS: https://fecfile-web-app-stage.app.cloud.gov CSRF_TRUSTED_ORIGINS: https://fecfile-web-app-stage.app.cloud.gov FFAPI_COOKIE_DOMAIN: app.cloud.gov LOGIN_REDIRECT_CLIENT_URL: https://fecfile-web-app-stage.app.cloud.gov From f15c4c40b39112ff6fd8d52f45fe4a7b9f493a8d Mon Sep 17 00:00:00 2001 From: Laura Beaufort Date: Fri, 28 Jun 2024 16:30:28 -0400 Subject: [PATCH 08/33] CSRF: Allow all *.fecfile.fec.gov origins --- django-backend/fecfiler/settings/base.py | 4 +--- django-backend/fecfiler/settings/local.py | 1 + manifests/manifest-dev-api.yml | 1 - manifests/manifest-prod-api.yml | 1 - manifests/manifest-stage-api.yml | 1 - 5 files changed, 2 insertions(+), 6 deletions(-) diff --git a/django-backend/fecfiler/settings/base.py b/django-backend/fecfiler/settings/base.py index 0f17fbbfef..a6d18300e6 100644 --- a/django-backend/fecfiler/settings/base.py +++ b/django-backend/fecfiler/settings/base.py @@ -27,9 +27,7 @@ LOG_FORMAT = env.get_credential("LOG_FORMAT", LINE) CSRF_COOKIE_DOMAIN = env.get_credential("FFAPI_COOKIE_DOMAIN") -CSRF_TRUSTED_ORIGINS = [ - env.get_credential("CSRF_TRUSTED_ORIGINS", "http://localhost:4200") -] +CSRF_TRUSTED_ORIGINS = ["https://*.fecfile.fec.gov"] """ Enables alternative log in method. diff --git a/django-backend/fecfiler/settings/local.py b/django-backend/fecfiler/settings/local.py index e8151bce5e..d73824659d 100644 --- a/django-backend/fecfiler/settings/local.py +++ b/django-backend/fecfiler/settings/local.py @@ -3,6 +3,7 @@ # These settings are for local development only. CORS_ALLOWED_ORIGIN_REGEXES.append("http://localhost:4200") # NOSONAR # noqa: F405 +CSRF_TRUSTED_ORIGINS.append("http://localhost:4200") # NOSONAR # noqa: F405 try: from .local import * # NOSONAR # noqa: F401, F403 diff --git a/manifests/manifest-dev-api.yml b/manifests/manifest-dev-api.yml index a5f5afcc2d..1292508938 100644 --- a/manifests/manifest-dev-api.yml +++ b/manifests/manifest-dev-api.yml @@ -17,7 +17,6 @@ applications: env: DISABLE_COLLECTSTATIC: 1 DJANGO_SETTINGS_MODULE: fecfiler.settings.production - CSRF_TRUSTED_ORIGINS: https://fecfile-web-app-dev.app.cloud.gov FFAPI_COOKIE_DOMAIN: app.cloud.gov LOGIN_REDIRECT_CLIENT_URL: https://fecfile-web-app-dev.app.cloud.gov LOGIN_REDIRECT_SERVER_URL: https://fecfile-web-api-dev.app.cloud.gov/api/v1/auth/login-redirect diff --git a/manifests/manifest-prod-api.yml b/manifests/manifest-prod-api.yml index d997949d6e..aeb0ae8b54 100644 --- a/manifests/manifest-prod-api.yml +++ b/manifests/manifest-prod-api.yml @@ -17,7 +17,6 @@ applications: env: DISABLE_COLLECTSTATIC: 1 DJANGO_SETTINGS_MODULE: fecfiler.settings.production - CSRF_TRUSTED_ORIGINS: https://fecfile-web-app-prod.app.cloud.gov FFAPI_COOKIE_DOMAIN: app.cloud.gov LOGIN_REDIRECT_CLIENT_URL: https://fecfile-web-app-prod.app.cloud.gov LOGIN_REDIRECT_SERVER_URL: https://fecfile-web-api-prod.app.cloud.gov/api/v1/auth/login-redirect diff --git a/manifests/manifest-stage-api.yml b/manifests/manifest-stage-api.yml index 976a47be26..0ca5274a51 100644 --- a/manifests/manifest-stage-api.yml +++ b/manifests/manifest-stage-api.yml @@ -17,7 +17,6 @@ applications: env: DISABLE_COLLECTSTATIC: 1 DJANGO_SETTINGS_MODULE: fecfiler.settings.production - CSRF_TRUSTED_ORIGINS: https://fecfile-web-app-stage.app.cloud.gov FFAPI_COOKIE_DOMAIN: app.cloud.gov LOGIN_REDIRECT_CLIENT_URL: https://fecfile-web-app-stage.app.cloud.gov LOGIN_REDIRECT_SERVER_URL: https://fecfile-web-api-stage.app.cloud.gov/api/v1/auth/login-redirect From a8f80c54e299c12cc7951d3f3050bc3f067cd4d4 Mon Sep 17 00:00:00 2001 From: Laura Beaufort Date: Fri, 28 Jun 2024 17:07:01 -0400 Subject: [PATCH 09/33] Add network policy with CONST values --- tasks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index 08aa6bc97e..d7c9766faa 100644 --- a/tasks.py +++ b/tasks.py @@ -10,6 +10,7 @@ APP_NAME = "fecfile-web-api" WEB_SERVICES_NAME = "fecfile-web-services" +PROXY_NAME = "fecfile-api-proxy" ORG_NAME = "fec-fecfileonline-prototyping" MANIFEST_LABEL = { @@ -195,7 +196,7 @@ def deploy(ctx, space=None, branch=None, login=False, help=False): # Allow proxy to connect to api via internal route add_network_policy = ctx.run( - "cf add-network-policy fecfile-api-proxy fecfile-web-api", + f"cf add-network-policy {PROXY_NAME} {APP_NAME}", echo=True, warn=True, ) From c8a7379c33f77452707cc65db96ec34aaab9fa4a Mon Sep 17 00:00:00 2001 From: Laura Beaufort Date: Fri, 28 Jun 2024 17:07:41 -0400 Subject: [PATCH 10/33] Update django-cors-headers --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9c2ad92bde..c6d19660f1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ decorator==5.1.1 dj-database-url==1.3.0 dj-static==0.0.6 Django==4.2.11 # when updating this, also update requirements/tox versions in fecgov/mozilla_django_oidc -django-cors-headers==3.13.0 +django-cors-headers==4.3.1 django-storages==1.13.1 djangorestframework==3.14.0 git+https://github.com/fecgov/fecfile-validate@0f8b966b623fbca644aebb2054fe4829eb0e0a93#egg=fecfile_validate&subdirectory=fecfile_validate_python From b7ca0ff7df5d1a8f794207318963ee1a5ba25881 Mon Sep 17 00:00:00 2001 From: Laura Beaufort Date: Fri, 28 Jun 2024 17:13:12 -0400 Subject: [PATCH 11/33] Update domain to fecfile.fec.gov --- django-backend/fecfiler/settings/production.py | 2 +- manifests/manifest-dev-api.yml | 8 ++++---- manifests/manifest-prod-api.yml | 8 ++++---- manifests/manifest-stage-api.yml | 8 ++++---- performance-testing/README.md | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/django-backend/fecfiler/settings/production.py b/django-backend/fecfiler/settings/production.py index e64237f9c1..18b5e1d75c 100644 --- a/django-backend/fecfiler/settings/production.py +++ b/django-backend/fecfiler/settings/production.py @@ -11,4 +11,4 @@ SESSION_COOKIE_HTTPONLY = True CSRF_COOKIE_SECURE = True -ALLOWED_HOSTS = [".fec.gov", ".app.cloud.gov"] +ALLOWED_HOSTS = [".fecfile.fec.gov"] diff --git a/manifests/manifest-dev-api.yml b/manifests/manifest-dev-api.yml index 1292508938..f629e2c575 100644 --- a/manifests/manifest-dev-api.yml +++ b/manifests/manifest-dev-api.yml @@ -17,10 +17,10 @@ applications: env: DISABLE_COLLECTSTATIC: 1 DJANGO_SETTINGS_MODULE: fecfiler.settings.production - FFAPI_COOKIE_DOMAIN: app.cloud.gov - LOGIN_REDIRECT_CLIENT_URL: https://fecfile-web-app-dev.app.cloud.gov - LOGIN_REDIRECT_SERVER_URL: https://fecfile-web-api-dev.app.cloud.gov/api/v1/auth/login-redirect - LOGOUT_REDIRECT_URL: https://fecfile-web-api-dev.app.cloud.gov/api/v1/auth/logout-redirect + FFAPI_COOKIE_DOMAIN: fecfile.fec.gov + LOGIN_REDIRECT_CLIENT_URL: https://dev-api.fecfile.fec.gov + LOGIN_REDIRECT_SERVER_URL: https://dev-api.fecfile.fec.gov/api/v1/auth/login-redirect + LOGOUT_REDIRECT_URL: https://dev-api.fecfile.fec.gov/api/v1/auth/logout-redirect FEC_API: https://api.open.fec.gov/v1/ SESSION_COOKIE_AGE: 64800 BP_PIP_VERSION: latest diff --git a/manifests/manifest-prod-api.yml b/manifests/manifest-prod-api.yml index aeb0ae8b54..b5b102e2d6 100644 --- a/manifests/manifest-prod-api.yml +++ b/manifests/manifest-prod-api.yml @@ -17,10 +17,10 @@ applications: env: DISABLE_COLLECTSTATIC: 1 DJANGO_SETTINGS_MODULE: fecfiler.settings.production - FFAPI_COOKIE_DOMAIN: app.cloud.gov - LOGIN_REDIRECT_CLIENT_URL: https://fecfile-web-app-prod.app.cloud.gov - LOGIN_REDIRECT_SERVER_URL: https://fecfile-web-api-prod.app.cloud.gov/api/v1/auth/login-redirect - LOGOUT_REDIRECT_URL: https://fecfile-web-api-prod.app.cloud.gov/api/v1/auth/logout-redirect + FFAPI_COOKIE_DOMAIN: fecfile.fec.gov + LOGIN_REDIRECT_CLIENT_URL: https://api.fecfile.fec.gov + LOGIN_REDIRECT_SERVER_URL: https://api.fecfile.fec.gov/api/v1/auth/login-redirect + LOGOUT_REDIRECT_URL: https://api.fecfile.fec.gov/api/v1/auth/logout-redirect FEC_API: https://api.open.fec.gov/v1/ SESSION_COOKIE_AGE: 64800 BP_PIP_VERSION: latest diff --git a/manifests/manifest-stage-api.yml b/manifests/manifest-stage-api.yml index 0ca5274a51..4a1d0b464e 100644 --- a/manifests/manifest-stage-api.yml +++ b/manifests/manifest-stage-api.yml @@ -17,10 +17,10 @@ applications: env: DISABLE_COLLECTSTATIC: 1 DJANGO_SETTINGS_MODULE: fecfiler.settings.production - FFAPI_COOKIE_DOMAIN: app.cloud.gov - LOGIN_REDIRECT_CLIENT_URL: https://fecfile-web-app-stage.app.cloud.gov - LOGIN_REDIRECT_SERVER_URL: https://fecfile-web-api-stage.app.cloud.gov/api/v1/auth/login-redirect - LOGOUT_REDIRECT_URL: https://fecfile-web-api-stage.app.cloud.gov/api/v1/auth/logout-redirect + FFAPI_COOKIE_DOMAIN: fecfile.fec.gov + LOGIN_REDIRECT_CLIENT_URL: https://stage-api.fecfile.fec.gov + LOGIN_REDIRECT_SERVER_URL: https://stage-api.fecfile.fec.gov/api/v1/auth/login-redirect + LOGOUT_REDIRECT_URL: https://stage-api.fecfile.fec.gov/api/v1/auth/logout-redirect FEC_API: https://api.open.fec.gov/v1/ SESSION_COOKIE_AGE: 64800 BP_PIP_VERSION: latest diff --git a/performance-testing/README.md b/performance-testing/README.md index a7b18ef902..7a8d16c736 100644 --- a/performance-testing/README.md +++ b/performance-testing/README.md @@ -46,7 +46,7 @@ Run the script with the `-h` flag for additional information. 2. Set the target API service for testing in [docker-compose.yml](https://github.com/fecgov/fecfile-web-api/blob/develop/docker-compose.yml#L118): - As an example, this is what you would set in order to target DEV: - - `-f /mnt/locust/locust_run.py --master -H https://fecfile-web-api-dev.app.cloud.gov` + - `-f /mnt/locust/locust_run.py --master -H https://dev-api.fecfile.fec.gov` ## Running Tests From 82897a1fb72095ba734bee9fba0f6f7477106770 Mon Sep 17 00:00:00 2001 From: Laura Beaufort Date: Fri, 28 Jun 2024 17:18:59 -0400 Subject: [PATCH 12/33] Set cache-control header, allow cache-control header --- django-backend/fecfiler/middleware.py | 8 ++++++++ django-backend/fecfiler/settings/base.py | 9 ++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 django-backend/fecfiler/middleware.py diff --git a/django-backend/fecfiler/middleware.py b/django-backend/fecfiler/middleware.py new file mode 100644 index 0000000000..eba446f528 --- /dev/null +++ b/django-backend/fecfiler/middleware.py @@ -0,0 +1,8 @@ +class HeaderMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + response = self.get_response(request) + response['cache-control'] = "no-cache, no-store" + return response diff --git a/django-backend/fecfiler/settings/base.py b/django-backend/fecfiler/settings/base.py index a6d18300e6..f167b16d28 100644 --- a/django-backend/fecfiler/settings/base.py +++ b/django-backend/fecfiler/settings/base.py @@ -82,6 +82,7 @@ MIDDLEWARE = [ "corsheaders.middleware.CorsMiddleware", "django.middleware.security.SecurityMiddleware", + "fecfiler.middleware.HeaderMiddleware", "fecfiler.authentication.middleware.TimeoutMiddleware.TimeoutMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", @@ -111,7 +112,13 @@ CORS_ALLOWED_ORIGIN_REGEXES = [ r"^https://(.*?).fecfile\.fec\.gov$" ] -CORS_ALLOW_HEADERS = default_headers + ("enctype", "token") + +CORS_ALLOW_HEADERS = ( + *default_headers, + "enctype", + "token", + "cache-control", +) CORS_ALLOW_CREDENTIALS = True From 50aaa61e95bfff19a172e92b1b658ee21abd64ab Mon Sep 17 00:00:00 2001 From: David Heitzer Date: Mon, 1 Jul 2024 16:45:18 -0400 Subject: [PATCH 13/33] 302 fix --- manifests/manifest-dev-api.yml | 2 +- manifests/manifest-prod-api.yml | 2 +- manifests/manifest-stage-api.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manifests/manifest-dev-api.yml b/manifests/manifest-dev-api.yml index f629e2c575..098d920053 100644 --- a/manifests/manifest-dev-api.yml +++ b/manifests/manifest-dev-api.yml @@ -18,7 +18,7 @@ applications: DISABLE_COLLECTSTATIC: 1 DJANGO_SETTINGS_MODULE: fecfiler.settings.production FFAPI_COOKIE_DOMAIN: fecfile.fec.gov - LOGIN_REDIRECT_CLIENT_URL: https://dev-api.fecfile.fec.gov + LOGIN_REDIRECT_CLIENT_URL: https://dev.fecfile.fec.gov LOGIN_REDIRECT_SERVER_URL: https://dev-api.fecfile.fec.gov/api/v1/auth/login-redirect LOGOUT_REDIRECT_URL: https://dev-api.fecfile.fec.gov/api/v1/auth/logout-redirect FEC_API: https://api.open.fec.gov/v1/ diff --git a/manifests/manifest-prod-api.yml b/manifests/manifest-prod-api.yml index b5b102e2d6..66da12bf06 100644 --- a/manifests/manifest-prod-api.yml +++ b/manifests/manifest-prod-api.yml @@ -18,7 +18,7 @@ applications: DISABLE_COLLECTSTATIC: 1 DJANGO_SETTINGS_MODULE: fecfiler.settings.production FFAPI_COOKIE_DOMAIN: fecfile.fec.gov - LOGIN_REDIRECT_CLIENT_URL: https://api.fecfile.fec.gov + LOGIN_REDIRECT_CLIENT_URL: https://fecfile.fec.gov LOGIN_REDIRECT_SERVER_URL: https://api.fecfile.fec.gov/api/v1/auth/login-redirect LOGOUT_REDIRECT_URL: https://api.fecfile.fec.gov/api/v1/auth/logout-redirect FEC_API: https://api.open.fec.gov/v1/ diff --git a/manifests/manifest-stage-api.yml b/manifests/manifest-stage-api.yml index 4a1d0b464e..5bf64539e3 100644 --- a/manifests/manifest-stage-api.yml +++ b/manifests/manifest-stage-api.yml @@ -18,7 +18,7 @@ applications: DISABLE_COLLECTSTATIC: 1 DJANGO_SETTINGS_MODULE: fecfiler.settings.production FFAPI_COOKIE_DOMAIN: fecfile.fec.gov - LOGIN_REDIRECT_CLIENT_URL: https://stage-api.fecfile.fec.gov + LOGIN_REDIRECT_CLIENT_URL: https://stage.fecfile.fec.gov LOGIN_REDIRECT_SERVER_URL: https://stage-api.fecfile.fec.gov/api/v1/auth/login-redirect LOGOUT_REDIRECT_URL: https://stage-api.fecfile.fec.gov/api/v1/auth/logout-redirect FEC_API: https://api.open.fec.gov/v1/ From 1a8cf85d76376bf739bd663512bb044ac1870854 Mon Sep 17 00:00:00 2001 From: Sasha Dresden Date: Tue, 9 Jul 2024 14:30:38 -0400 Subject: [PATCH 14/33] Pinning sqlparse to 0.5.0 for snyk security check --- requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requirements.txt b/requirements.txt index 0a8109cd8f..f9fe1a58c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,3 +27,6 @@ git+https://github.com/fecgov/mozilla-django-oidc.git@5a813c15fba5463a2907b2e22a zeep==4.2.1 django-structlog[celery]==7.1.0 rich==13.7.0 + +# Pinning sqlparse to 0.5.0 as it is a sub-dependency that needs to be upgraded for security +sqlparse==0.5.0 \ No newline at end of file From 0110e1519d91b04ba941102f1af006c5b8e2d935 Mon Sep 17 00:00:00 2001 From: David Heitzer Date: Wed, 10 Jul 2024 00:28:48 -0400 Subject: [PATCH 15/33] 1991 fix bug --- ...9_update_calculate_loan_payment_to_date.py | 63 ++++++++++++++++--- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/django-backend/fecfiler/transactions/migrations/0009_update_calculate_loan_payment_to_date.py b/django-backend/fecfiler/transactions/migrations/0009_update_calculate_loan_payment_to_date.py index 7afcd56cb1..2efb7ec175 100644 --- a/django-backend/fecfiler/transactions/migrations/0009_update_calculate_loan_payment_to_date.py +++ b/django-backend/fecfiler/transactions/migrations/0009_update_calculate_loan_payment_to_date.py @@ -4,9 +4,9 @@ def update_existing_rows(apps, schema_editor): transaction = apps.get_model("transactions", "Transaction") types = [ - 'LOAN_RECEIVED_FROM_INDIVIDUAL', - 'LOAN_RECEIVED_FROM BANK', - 'LOAN_BY_COMMITTEE', + "LOAN_RECEIVED_FROM_INDIVIDUAL", + "LOAN_RECEIVED_FROM BANK", + "LOAN_BY_COMMITTEE", ] for row in transaction.objects.filter(transaction_type_identifier__in=types): row.save() @@ -15,10 +15,7 @@ def update_existing_rows(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ( - "transactions", - "0008_transaction__calendar_ytd_per_election_office_and_more" - ), + ("transactions", "0008_transaction__calendar_ytd_per_election_office_and_more"), ] operations = [ @@ -64,5 +61,57 @@ class Migration(migrations.Migration): $$ LANGUAGE plpgsql; """ ), + migrations.RunSQL( + """ + CREATE OR REPLACE FUNCTION calculate_aggregates() + RETURNS TRIGGER AS $$ + DECLARE + sql_committee_id TEXT; + temp_table_name TEXT; + BEGIN + sql_committee_id := REPLACE(NEW.committee_account_id::TEXT, '-', '_'); + temp_table_name := 'temp_' || REPLACE(uuid_generate_v4()::TEXT, '-', '_'); + RAISE NOTICE 'TESTING TRIGGER'; + + -- If schedule_c2_id or schedule_d_id is not null, stop processing + IF NEW.schedule_c2_id IS NOT NULL OR NEW.schedule_d_id IS NOT NULL + THEN + RETURN NEW; + END IF; + + IF NEW.schedule_a_id IS NOT NULL OR NEW.schedule_b_id IS NOT NULL + THEN + PERFORM calculate_entity_aggregates( + NEW, sql_committee_id, temp_table_name || 'NEW'); + IF TG_OP = 'UPDATE' + AND NEW.contact_1_id <> OLD.contact_1_id + THEN + PERFORM calculate_entity_aggregates( + OLD, sql_committee_id, temp_table_name || 'OLD'); + END IF; + END IF; + + IF NEW.schedule_c_id IS NOT NULL OR NEW.schedule_c1_id IS NOT NULL OR NEW.transaction_type_identifier = 'LOAN_REPAYMENT_MADE' + THEN + PERFORM calculate_loan_payment_to_date( + NEW, sql_committee_id, temp_table_name || 'NEW'); + END IF; + + IF NEW.schedule_e_id IS NOT NULL + THEN + PERFORM calculate_calendar_ytd_per_election_office( + NEW, sql_committee_id, temp_table_name || 'NEW'); + IF TG_OP = 'UPDATE' + THEN + PERFORM calculate_calendar_ytd_per_election_office( + OLD, sql_committee_id, temp_table_name || 'OLD'); + END IF; + END IF; + + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + """ + ), migrations.RunPython(update_existing_rows), ] From 549f9fc1f67f40e47b58ff6d53ce9ed72900fa0b Mon Sep 17 00:00:00 2001 From: Matt Travers Date: Thu, 11 Jul 2024 17:51:49 -0400 Subject: [PATCH 16/33] Move temp table creation into its own function --- ...9_update_calculate_loan_payment_to_date.py | 171 ++++++++++++++++-- 1 file changed, 157 insertions(+), 14 deletions(-) diff --git a/django-backend/fecfiler/transactions/migrations/0009_update_calculate_loan_payment_to_date.py b/django-backend/fecfiler/transactions/migrations/0009_update_calculate_loan_payment_to_date.py index 2efb7ec175..b4660cbb67 100644 --- a/django-backend/fecfiler/transactions/migrations/0009_update_calculate_loan_payment_to_date.py +++ b/django-backend/fecfiler/transactions/migrations/0009_update_calculate_loan_payment_to_date.py @@ -19,15 +19,154 @@ class Migration(migrations.Migration): ] operations = [ + migrations.RunSQL( + """ + CREATE OR REPLACE FUNCTION get_temp_tablename() + RETURNS TEXT AS $$ + BEGIN + RETURN 'temp_' || REPLACE(uuid_generate_v4()::TEXT, '-', '_'); + END; + $$ LANGUAGE plpgsql; + """ + ), + migrations.RunSQL( + """ + CREATE OR REPLACE FUNCTION calculate_entity_aggregates( + txn RECORD, + sql_committee_id TEXT + ) + RETURNS VOID AS $$ + DECLARE + schedule_date DATE; + temp_table_name TEXT; + BEGIN + temp_table_name := get_temp_tablename(); + IF txn.schedule_a_id IS NOT NULL THEN + SELECT contribution_date + INTO schedule_date + FROM transactions_schedulea + WHERE id = txn.schedule_a_id; + ELSIF txn.schedule_b_id IS NOT NULL THEN + SELECT expenditure_date + INTO schedule_date + FROM transactions_scheduleb + WHERE id = txn.schedule_b_id; + END IF; + + EXECUTE ' + CREATE TEMPORARY TABLE ' || temp_table_name || ' + ON COMMIT DROP AS + SELECT + id, + SUM(effective_amount) OVER (ORDER BY date, created) + AS new_sum + FROM transaction_view__' || sql_committee_id || ' + WHERE + contact_1_id = $1 + AND EXTRACT(YEAR FROM date) = $2 + AND aggregation_group = $3 + AND force_unaggregated IS NOT TRUE; + + UPDATE transactions_transaction AS t + SET aggregate = tt.new_sum + FROM ' || temp_table_name || ' AS tt + WHERE t.id = tt.id; + ' + USING + txn.contact_1_id, + EXTRACT(YEAR FROM schedule_date), + txn.aggregation_group; + END; + $$ LANGUAGE plpgsql; + """ + ), + migrations.RunSQL( + """ + CREATE OR REPLACE FUNCTION calculate_calendar_ytd_per_election_office( + txn RECORD, + sql_committee_id TEXT + ) + RETURNS VOID AS $$ + DECLARE + schedule_date DATE; + v_election_code TEXT; + v_candidate_office TEXT; + v_candidate_state TEXT; + v_candidate_district TEXT; + temp_table_name TEXT; + BEGIN + temp_table_name := get_temp_tablename(); + SELECT COALESCE(disbursement_date, dissemination_date) + INTO schedule_date FROM transactions_schedulee + WHERE id = txn.schedule_e_id; + SELECT election_code + INTO v_election_code + FROM transactions_schedulee + WHERE id = txn.schedule_e_id; + SELECT candidate_office, candidate_state, candidate_district + INTO v_candidate_office, v_candidate_state, v_candidate_district + FROM contacts WHERE id = txn.contact_2_id; + + EXECUTE ' + CREATE TEMPORARY TABLE ' || temp_table_name || ' + ON COMMIT DROP AS + SELECT + t.id, + SUM(t.effective_amount) OVER + (ORDER BY t.date, t.created) AS new_sum + FROM transactions_schedulee e + JOIN transaction_view__' || sql_committee_id || ' t + ON e.id = t.schedule_e_id + JOIN contacts c + ON t.contact_2_id = c.id + WHERE + e.election_code = $1 + AND c.candidate_office = $2 + AND ( + c.candidate_state = $3 + OR ( + c.candidate_state IS NULL + AND $3 = '''' + ) + ) + AND ( + c.candidate_district = $4 + OR ( + c.candidate_district IS NULL + AND $4 = '''' + ) + ) + AND EXTRACT(YEAR FROM t.date) = $5 + AND aggregation_group = $6 + AND force_unaggregated IS NOT TRUE; + + UPDATE transactions_transaction AS t + SET _calendar_ytd_per_election_office = tt.new_sum + FROM ' || temp_table_name || ' AS tt + WHERE t.id = tt.id; + ' + USING + v_election_code, + v_candidate_office, + COALESCE(v_candidate_state, ''), + COALESCE(v_candidate_district, ''), + EXTRACT(YEAR FROM schedule_date), + txn.aggregation_group; + END; + $$ LANGUAGE plpgsql; + """ + ), migrations.RunSQL( """ CREATE OR REPLACE FUNCTION calculate_loan_payment_to_date( txn RECORD, - sql_committee_id TEXT, - temp_table_name TEXT + sql_committee_id TEXT ) RETURNS VOID AS $$ + DECLARE + temp_table_name TEXT; BEGIN + temp_table_name := get_temp_tablename(); EXECUTE ' CREATE TEMPORARY TABLE ' || temp_table_name || ' ON COMMIT DROP AS @@ -67,11 +206,8 @@ class Migration(migrations.Migration): RETURNS TRIGGER AS $$ DECLARE sql_committee_id TEXT; - temp_table_name TEXT; BEGIN sql_committee_id := REPLACE(NEW.committee_account_id::TEXT, '-', '_'); - temp_table_name := 'temp_' || REPLACE(uuid_generate_v4()::TEXT, '-', '_'); - RAISE NOTICE 'TESTING TRIGGER'; -- If schedule_c2_id or schedule_d_id is not null, stop processing IF NEW.schedule_c2_id IS NOT NULL OR NEW.schedule_d_id IS NOT NULL @@ -81,30 +217,29 @@ class Migration(migrations.Migration): IF NEW.schedule_a_id IS NOT NULL OR NEW.schedule_b_id IS NOT NULL THEN - PERFORM calculate_entity_aggregates( - NEW, sql_committee_id, temp_table_name || 'NEW'); + PERFORM calculate_entity_aggregates(NEW, sql_committee_id); IF TG_OP = 'UPDATE' AND NEW.contact_1_id <> OLD.contact_1_id THEN - PERFORM calculate_entity_aggregates( - OLD, sql_committee_id, temp_table_name || 'OLD'); + PERFORM calculate_entity_aggregates(OLD, sql_committee_id); END IF; END IF; - IF NEW.schedule_c_id IS NOT NULL OR NEW.schedule_c1_id IS NOT NULL OR NEW.transaction_type_identifier = 'LOAN_REPAYMENT_MADE' + IF NEW.schedule_c_id IS NOT NULL + OR NEW.schedule_c1_id IS NOT NULL + OR NEW.transaction_type_identifier = 'LOAN_REPAYMENT_MADE' THEN - PERFORM calculate_loan_payment_to_date( - NEW, sql_committee_id, temp_table_name || 'NEW'); + PERFORM calculate_loan_payment_to_date(NEW, sql_committee_id); END IF; IF NEW.schedule_e_id IS NOT NULL THEN PERFORM calculate_calendar_ytd_per_election_office( - NEW, sql_committee_id, temp_table_name || 'NEW'); + NEW, sql_committee_id); IF TG_OP = 'UPDATE' THEN PERFORM calculate_calendar_ytd_per_election_office( - OLD, sql_committee_id, temp_table_name || 'OLD'); + OLD, sql_committee_id); END IF; END IF; @@ -113,5 +248,13 @@ class Migration(migrations.Migration): $$ LANGUAGE plpgsql; """ ), + migrations.RunSQL( + """ + -- Drop prior versions of these functions + DROP FUNCTION calculate_entity_aggregates(RECORD, TEXT, TEXT); + DROP FUNCTION calculate_calendar_ytd_per_election_office(RECORD, TEXT, TEXT); + DROP FUNCTION calculate_loan_payment_to_date(RECORD, TEXT, TEXT); + """ + ), migrations.RunPython(update_existing_rows), ] From 8aa53a50c8f3baff9a333ec1c1e4e939cd6f473c Mon Sep 17 00:00:00 2001 From: Matt Travers Date: Fri, 12 Jul 2024 18:12:59 -0400 Subject: [PATCH 17/33] Add unit test for loan_payment_to_date calculation --- .../transactions/tests/test_manager.py | 68 +++++++++++++++++++ .../fecfiler/transactions/tests/utils.py | 13 +++- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/django-backend/fecfiler/transactions/tests/test_manager.py b/django-backend/fecfiler/transactions/tests/test_manager.py index 03a6adb348..36561b61d7 100644 --- a/django-backend/fecfiler/transactions/tests/test_manager.py +++ b/django-backend/fecfiler/transactions/tests/test_manager.py @@ -12,7 +12,9 @@ create_schedule_a, create_ie, create_debt, + create_loan, ) +from fecfiler.transactions.schedule_c.utils import carry_forward_loans from decimal import Decimal from django.db import transaction import structlog @@ -348,3 +350,69 @@ def test_line_label(self): self.assertEqual(view[1].line_label, "11(a)(ii)") self.assertEqual(view[2].line_label, "11(a)(ii)") self.assertEqual(view[3].line_label, "21(b)") + + def test_loan_payment_to_date(self): + m1_report = create_form3x(self.committee, "2024-01-01", "2024-01-31", {}) + loan = create_loan( + self.committee, + self.contact_1, + "5000.00", + "2024-07-01", + "7%", + loan_incurred_date="2024-01-01", + report=m1_report, + ) + create_schedule_b( + "LOAN_REPAYMENT_MADE", + self.committee, + self.contact_1, + "2024-01-02", + "1000.00", + loan_id=loan.id, + report=m1_report, + ) + create_schedule_b( + "LOAN_REPAYMENT_MADE", + self.committee, + self.contact_1, + "2024-01-03", + "500.00", + loan_id=loan.id, + report=m1_report, + ) + view: QuerySet = Transaction.objects.transaction_view() + transactions = view.filter(committee_account_id=self.committee.id).order_by( + "date" + ) + self.assertEqual(transactions[0].amount, Decimal("5000.00")) + self.assertEqual(transactions[0].loan_payment_to_date, Decimal("1500.00")) + self.assertEqual(transactions[1].amount, Decimal("1000.00")) + self.assertEqual(transactions[2].amount, Decimal("500.00")) + + # Pull loan forward and make sure payments are still correct + + m2_report = create_form3x(self.committee, "2024-02-01", "2024-02-28", {}) + carry_forward_loans(m2_report) + carried_forward_loan = view.filter( + committee_account_id=self.committee.id + ).order_by("created").last() + create_schedule_b( + "LOAN_REPAYMENT_MADE", + self.committee, + self.contact_1, + "2024-02-05", + "600.00", + loan_id=carried_forward_loan.id, + report=m2_report, + ) + transactions = view.filter(committee_account_id=self.committee.id).order_by( + "created" + ) + self.assertEqual(transactions[0].amount, Decimal("5000.00")) + self.assertEqual(transactions[0].loan_payment_to_date, Decimal("1500.00")) + self.assertEqual(transactions[1].amount, Decimal("1000.00")) + self.assertEqual(transactions[2].amount, Decimal("500.00")) + self.assertEqual(transactions[4].amount, Decimal("5000.00")) + self.assertEqual(transactions[4].loan_payment_to_date, Decimal("2100.00")) + self.assertEqual(transactions[5].amount, Decimal("600.00")) + diff --git a/django-backend/fecfiler/transactions/tests/utils.py b/django-backend/fecfiler/transactions/tests/utils.py index 96cf8251c6..7503cf3282 100644 --- a/django-backend/fecfiler/transactions/tests/utils.py +++ b/django-backend/fecfiler/transactions/tests/utils.py @@ -49,6 +49,7 @@ def create_schedule_b( form_type="SB", memo_code=False, report=None, + loan_id=None ): return create_test_transaction( type, @@ -58,7 +59,11 @@ def create_schedule_b( group=group, report=report, schedule_data={"expenditure_date": date, "expenditure_amount": amount}, - transaction_data={"_form_type": form_type, "memo_code": memo_code}, + transaction_data={ + "_form_type": form_type, + "memo_code": memo_code, + "loan_id": loan_id + }, ) @@ -122,7 +127,10 @@ def create_loan( secured=False, type="LOAN_RECEIVED_FROM_INDIVIDUAL", form_type="SC/9", + loan_incurred_date=None, + report=None, ): + report_coverage_through_date = report.coverage_through_date if report else None return create_test_transaction( type, ScheduleC, @@ -133,8 +141,11 @@ def create_loan( "loan_due_date": loan_due_date, "loan_interest_rate": loan_interest_rate, "secured": secured, + "loan_incurred_date": loan_incurred_date, + "report_coverage_through_date": report_coverage_through_date, }, transaction_data={"_form_type": form_type}, + report=report, ) From 4b5315a49133e47c2c0a896c265db63391be9e7a Mon Sep 17 00:00:00 2001 From: Matt Travers Date: Fri, 12 Jul 2024 18:19:32 -0400 Subject: [PATCH 18/33] Fix lint issue --- django-backend/fecfiler/transactions/tests/test_manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/django-backend/fecfiler/transactions/tests/test_manager.py b/django-backend/fecfiler/transactions/tests/test_manager.py index 36561b61d7..57c6c65c7f 100644 --- a/django-backend/fecfiler/transactions/tests/test_manager.py +++ b/django-backend/fecfiler/transactions/tests/test_manager.py @@ -415,4 +415,3 @@ def test_loan_payment_to_date(self): self.assertEqual(transactions[4].amount, Decimal("5000.00")) self.assertEqual(transactions[4].loan_payment_to_date, Decimal("2100.00")) self.assertEqual(transactions[5].amount, Decimal("600.00")) - From 68975607f8efff0096f9c62231ed70e660660e12 Mon Sep 17 00:00:00 2001 From: Sasha Dresden Date: Mon, 15 Jul 2024 14:16:22 -0400 Subject: [PATCH 19/33] Create performance tag for unit tests and create performance test for creating dot fec --- django-backend/fecfiler/settings/base.py | 7 ++- django-backend/fecfiler/test_runner.py | 14 +++++ .../fecfiler/web_services/test_tasks.py | 61 +++++++++++++++++-- 3 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 django-backend/fecfiler/test_runner.py diff --git a/django-backend/fecfiler/settings/base.py b/django-backend/fecfiler/settings/base.py index f167b16d28..ddc92782fa 100644 --- a/django-backend/fecfiler/settings/base.py +++ b/django-backend/fecfiler/settings/base.py @@ -109,9 +109,7 @@ }, ] -CORS_ALLOWED_ORIGIN_REGEXES = [ - r"^https://(.*?).fecfile\.fec\.gov$" -] +CORS_ALLOWED_ORIGIN_REGEXES = [r"^https://(.*?).fecfile\.fec\.gov$"] CORS_ALLOW_HEADERS = ( *default_headers, @@ -364,3 +362,6 @@ def get_logging_processors(): MOCK_OPENFEC_REDIS_URL = env.get_credential("REDIS_URL") else: MOCK_OPENFEC_REDIS_URL = None + + +TEST_RUNNER = "fecfiler.test_runner.CustomTestRunner" diff --git a/django-backend/fecfiler/test_runner.py b/django-backend/fecfiler/test_runner.py new file mode 100644 index 0000000000..7e5478183e --- /dev/null +++ b/django-backend/fecfiler/test_runner.py @@ -0,0 +1,14 @@ +from django.test.runner import DiscoverRunner + + +class CustomTestRunner(DiscoverRunner): + def __init__(self, *args, **kwargs): + # Automatically exclude "performance" tagged tickets unless specifically invoked + if "tags" not in kwargs.keys(): + exclude = kwargs["exclude_tags"] + if exclude is None: + exclude = [] + exclude.append("performance") + kwargs["exclude_tags"] = exclude + + super().__init__(*args, **kwargs) diff --git a/django-backend/fecfiler/web_services/test_tasks.py b/django-backend/fecfiler/web_services/test_tasks.py index d87903e8ca..b818cb2b95 100644 --- a/django-backend/fecfiler/web_services/test_tasks.py +++ b/django-backend/fecfiler/web_services/test_tasks.py @@ -1,4 +1,6 @@ -from django.test import TestCase +import datetime +import timeit +from django.test import TestCase, tag from .tasks import create_dot_fec, submit_to_fec, submit_to_webprint from .models import ( DotFEC, @@ -15,6 +17,13 @@ from fecfiler.reports.tests.utils import create_form3x from fecfiler.contacts.tests.utils import create_test_individual_contact from fecfiler.transactions.tests.utils import create_schedule_a +from fecfiler.transactions.schedule_a.models import ScheduleA +from fecfiler.transactions.models import Transaction +from fecfiler.reports.models import ReportTransaction + +import structlog + +logger = structlog.get_logger(__name__) class TasksTestCase(TestCase): @@ -35,10 +44,9 @@ def setUp(self): "123.45", "GENERAL", "SA11AI", + itemized=True, + report=self.f3x, ) - self.transaction.reports.add(self.f3x) - self.transaction.force_itemized = True - self.transaction.save() """ CREATE DOT FEC TESTS @@ -60,6 +68,33 @@ def test_create_dot_fec(self): if result_dot_fec.exists(): result_dot_fec.unlink() + @tag("performance") + def test_load(self): + num_a = 2000 + transactions = [] + + for _ in range(num_a): + t = create_schedule_a( + "INDIVIDUAL_RECEIPT", + self.committee, + self.contact_1, + "2023-01-05", + "123.45", + "GENERAL", + "SA11AI", + itemized=True, + report=self.f3x, + ) + transactions.append(t) + + self.assertEqual(len(transactions), num_a) + start_time = timeit.default_timer() + dot_fec_id = create_dot_fec(str(self.f3x.id), None, None, True) + end_time = timeit.default_timer() + execution_time = end_time - start_time + logger.info(f"Execution time: {execution_time:.4f} seconds") + self.assertIsNotNone(dot_fec_id) + """ SUBMIT TO FEC TESTS """ @@ -152,3 +187,21 @@ def test_submit_to_webprint(self): self.assertEqual( webprint_submission.fec_image_url, "https://www.fec.gov/static/img/seal.svg" ) + + +from django.db import connection + + +def bulk_insert(values): + base_sql = "INSERT INTO transactions_transaction (transaction_type_identifier, aggregration_group, _form_type) VALUES" + values_sql = [] + values_data = [] + + for value_list in values: + placeholders = ["%s" for _ in range(len(value_list))] + values_sql.append(f"({', '.join(placeholders)})") + values_data.extend(value_list) + + sql = f"{base_sql} {', '.join(values_sql)}" + with connection.cursor() as cursor: + cursor.executemany(sql, [values_data]) From d638a60508001c7fb38aa7f9ea621f857ea3ca89 Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Tue, 16 Jul 2024 08:57:48 -0400 Subject: [PATCH 20/33] Updates fixture generation to work with new aggregation calculation --- .../bulk-testing-data-fixture-generator.py | 133 ++++++++++++------ 1 file changed, 90 insertions(+), 43 deletions(-) rename {bin => performance-testing}/bulk-testing-data-fixture-generator.py (77%) diff --git a/bin/bulk-testing-data-fixture-generator.py b/performance-testing/bulk-testing-data-fixture-generator.py similarity index 77% rename from bin/bulk-testing-data-fixture-generator.py rename to performance-testing/bulk-testing-data-fixture-generator.py index 5a92a5d3ad..db1f023226 100644 --- a/bin/bulk-testing-data-fixture-generator.py +++ b/performance-testing/bulk-testing-data-fixture-generator.py @@ -125,7 +125,7 @@ def random_string(length, use_letters=True, use_digits=False, use_punctuation=Fa if use_digits: character_pool += string.digits if use_punctuation: - character_pool += string.punctuation + character_pool += "!.?$@,#+=%&" if len(character_pool) == 0: return "" @@ -303,6 +303,9 @@ def get_transaction( "contact_1_id": f"{contact_id}", "aggregation_group": "GENERAL", "transaction_type_identifier": "INDIVIDUAL_RECEIPT", + "aggregate": 0, + "_calendar_ytd_per_election_office": None, + "loan_payment_to_date": None, "created": "2024-01-01T00:00:00.000Z", "updated": "2024-01-01T00:00:00.000Z" } @@ -339,14 +342,15 @@ def create_transaction(committee_uuid, contact_id, report): ] -def get_records(): - committees = [ - create_committee() for _ in range(4) - ] - committees.append(create_committee(PRIMARY_COMMITTEE_FEC_ID, PRIMARY_COMMITTEE_UUID)) +def create_records(transaction_count, report_count, contact_count, committee_count): + committees = [create_committee(PRIMARY_COMMITTEE_FEC_ID, PRIMARY_COMMITTEE_UUID)] + for _ in range(max(committee_count, 0)): + committees.append(create_committee()) + committee_records = {} for comm in committees: - committee_records[get_record_uuid(comm)] = { + committee_uuid = get_record_uuid(comm) + committee_records[committee_uuid] = { "committee_record": comm, "form_3xs": [], "reports": [], @@ -355,32 +359,27 @@ def get_records(): "transactions": [], "transaction_reports": [] } - - for _ in range(50): - committee = choice(committees) - committee_uuid = get_record_uuid(committee) - form_3x, report = create_report(committee_uuid) - committee_records[committee_uuid]["form_3xs"].append(form_3x) - committee_records[committee_uuid]["reports"].append(report) - - for _ in range(2395): - committee = choice(committees) - committee_uuid = get_record_uuid(committee) - contact = create_contact(committee_uuid) - committee_records[committee_uuid]["contacts"].append(contact) - - for _ in range(32500): - committee = choice(committees) - committee_uuid = get_record_uuid(committee) - report = choice(committee_records[committee_uuid]["reports"]) - contact = choice(committee_records[committee_uuid]["contacts"]) - contact_id = get_record_uuid(contact) - sched, trans, trans_rep = create_transaction( - committee_uuid, contact_id, report - ) - committee_records[committee_uuid]["sched_transactions"].append(sched) - committee_records[committee_uuid]["transactions"].append(trans) - committee_records[committee_uuid]["transaction_reports"].append(trans_rep) + committee_record = committee_records[committee_uuid] + + for _ in range(max(report_count, 1)): + form_3x, report = create_report(committee_uuid) + committee_record["form_3xs"].append(form_3x) + committee_record["reports"].append(report) + + for _ in range(max(contact_count, 1)): + contact = create_contact(committee_uuid) + committee_record["contacts"].append(contact) + + for _ in range(max(transaction_count, 1)): + report = choice(committee_record["reports"]) + contact = choice(committee_record["contacts"]) + contact_id = get_record_uuid(contact) + sched, trans, trans_rep = create_transaction( + committee_uuid, contact_id, report + ) + committee_record["sched_transactions"].append(sched) + committee_record["transactions"].append(trans) + committee_record["transaction_reports"].append(trans_rep) return committee_records @@ -390,8 +389,10 @@ def prepare_records(records): for c in records.values(): if c["committee_record"]["fields"]["committee_id"] != PRIMARY_COMMITTEE_FEC_ID: out_records.append(c["committee_record"]) + + for c in records.values(): out_records += ( - [c["form_3xs"]] + c["form_3xs"] + c["reports"] + c["contacts"] + c["sched_transactions"] @@ -417,13 +418,59 @@ def save_records_to_fixture(records): file.close() -records = get_records() -sorted_records = prepare_records(records) -save_records_to_fixture(sorted_records) -print(PRIMARY_COMMITTEE_FEC_ID, PRIMARY_COMMITTEE_UUID) -for c in records.values(): - print( - c["committee_record"]["fields"]["id"], - c["committee_record"]["fields"]["committee_id"] +def create_fixture(transactions=1000, reports=5, contacts=100, committees=1): + records = create_records(transactions, reports, contacts, committees) + sorted_records = prepare_records(records) + save_records_to_fixture(sorted_records) + + return sorted_records + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description="""This script generates json test data for use in performance testing.""" + ) + """ + Creating committees with a fixture leads to a transaction_view related error when + loading the fixture into the database. + """ + # parser.add_argument( + # "--committees", + # default=0, + # type=int, + # help="""The number of committees in addition to the testing committee + # to be created. Defaults to zero (0).""" + # ) + parser.add_argument( + "--reports", + default=5, + type=int, + help="""The number of form-3x reports to be created in each committee. + Reports are comprised of two records each. Defaults to five (5).""", + ) + parser.add_argument( + "--contacts", + default=100, + type=int, + help="""The number of contacts to be created in each committee. + Defaults to one hundred (100).""" ) -print(f"Generated fixture with {'{:,}'.format(len(sorted_records))} records") + parser.add_argument( + "--transactions", + default=1000, + type=int, + help="""The number of transactions to be created in each committee. + Transactions are comprised of three records each. Defaults to one thousand (1,000).""" + ) + args = parser.parse_args() + + sorted_records = create_fixture( + args.transactions, + args.reports, + args.contacts, + committees=0 # args.committees + ) + print(f"Generated fixture with {'{:,}'.format(len(sorted_records))} records") + From 42f544a2c4e9b927d38a1b0fd0621e71e5b7e19f Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Tue, 16 Jul 2024 09:15:05 -0400 Subject: [PATCH 21/33] Updates README --- performance-testing/README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/performance-testing/README.md b/performance-testing/README.md index 7a8d16c736..2b38812471 100644 --- a/performance-testing/README.md +++ b/performance-testing/README.md @@ -117,3 +117,22 @@ Once set up, silk profiling will run automatically as the API receives and proce To view the results, visit the API's `/silk` endpoint (for local development: `localhost:8080/silk/`) If setting up from scratch or looking for usage instructions, you can find documentation [here](https://github.com/jazzband/django-silk?tab=readme-ov-file#installation). + + +# Creating and loading bulk data with fixtures + +Fixtures are .json files that can be used to load data into the database. Loading data with fixtures is far faster than +creating records with individual requests, making it especially useful for preparing a database for ad-hoc performance testing. + +A script has been provided for generating fixtures with specific numbers of records. You can run the script with +``` + python bulk-testing-data-fixture-generator.py +``` +Running the script with the `-h` or `--help` flags will provide additional information. + +Once you have a fixture, you can load it into the database by performing the following steps: + +1a. (For Local) Use `docker exec -it fecfile-api /bin/bash` to enter the docker container. +1b. (For Cloud.gov or Circle CI) ssh into your docker instance of choice. +2. (Cloud.gov only) use `/tmp/lifecycle/shell` to establish a shell session. +3. Run `python manage.py loaddata FIXTURE-NAME` to load your fixture. From 137fdfe68365d32ca4e737d2947a84273258f576 Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Tue, 16 Jul 2024 09:20:35 -0400 Subject: [PATCH 22/33] Linting pass --- performance-testing/README.md | 5 +++-- .../bulk-testing-data-fixture-generator.py | 9 +++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/performance-testing/README.md b/performance-testing/README.md index 2b38812471..d5d14131c6 100644 --- a/performance-testing/README.md +++ b/performance-testing/README.md @@ -132,7 +132,8 @@ Running the script with the `-h` or `--help` flags will provide additional infor Once you have a fixture, you can load it into the database by performing the following steps: -1a. (For Local) Use `docker exec -it fecfile-api /bin/bash` to enter the docker container. -1b. (For Cloud.gov or Circle CI) ssh into your docker instance of choice. +1. Enter a fecfile-api docker container +- (For Local) Use `docker exec -it fecfile-api /bin/bash` +- (For Cloud.gov or Circle CI) ssh into your docker instance of choice. 2. (Cloud.gov only) use `/tmp/lifecycle/shell` to establish a shell session. 3. Run `python manage.py loaddata FIXTURE-NAME` to load your fixture. diff --git a/performance-testing/bulk-testing-data-fixture-generator.py b/performance-testing/bulk-testing-data-fixture-generator.py index db1f023226..5609bbaa3f 100644 --- a/performance-testing/bulk-testing-data-fixture-generator.py +++ b/performance-testing/bulk-testing-data-fixture-generator.py @@ -430,7 +430,8 @@ def create_fixture(transactions=1000, reports=5, contacts=100, committees=1): import argparse parser = argparse.ArgumentParser( - description="""This script generates json test data for use in performance testing.""" + description="""This script generates json test data for + use in performance testing.""" ) """ Creating committees with a fixture leads to a transaction_view related error when @@ -462,7 +463,8 @@ def create_fixture(transactions=1000, reports=5, contacts=100, committees=1): default=1000, type=int, help="""The number of transactions to be created in each committee. - Transactions are comprised of three records each. Defaults to one thousand (1,000).""" + Transactions are comprised of three records each. + Defaults to one thousand (1,000).""" ) args = parser.parse_args() @@ -470,7 +472,6 @@ def create_fixture(transactions=1000, reports=5, contacts=100, committees=1): args.transactions, args.reports, args.contacts, - committees=0 # args.committees + committees=0 # args.committees ) print(f"Generated fixture with {'{:,}'.format(len(sorted_records))} records") - From f83f88205b1034d5d398a361db61badfa2c58725 Mon Sep 17 00:00:00 2001 From: Sasha Dresden Date: Tue, 16 Jul 2024 11:23:45 -0400 Subject: [PATCH 23/33] Add extra conditions to make sure performance tests only run when performance tag passed --- django-backend/fecfiler/test_runner.py | 6 ++++- .../fecfiler/web_services/test_tasks.py | 22 ------------------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/django-backend/fecfiler/test_runner.py b/django-backend/fecfiler/test_runner.py index 7e5478183e..d43c4e6910 100644 --- a/django-backend/fecfiler/test_runner.py +++ b/django-backend/fecfiler/test_runner.py @@ -4,7 +4,11 @@ class CustomTestRunner(DiscoverRunner): def __init__(self, *args, **kwargs): # Automatically exclude "performance" tagged tickets unless specifically invoked - if "tags" not in kwargs.keys(): + if ( + "tags" not in kwargs.keys() + or kwargs["tags"] is None + or "performance" not in kwargs["tags"] + ): exclude = kwargs["exclude_tags"] if exclude is None: exclude = [] diff --git a/django-backend/fecfiler/web_services/test_tasks.py b/django-backend/fecfiler/web_services/test_tasks.py index b818cb2b95..2c54049efc 100644 --- a/django-backend/fecfiler/web_services/test_tasks.py +++ b/django-backend/fecfiler/web_services/test_tasks.py @@ -1,4 +1,3 @@ -import datetime import timeit from django.test import TestCase, tag from .tasks import create_dot_fec, submit_to_fec, submit_to_webprint @@ -17,9 +16,6 @@ from fecfiler.reports.tests.utils import create_form3x from fecfiler.contacts.tests.utils import create_test_individual_contact from fecfiler.transactions.tests.utils import create_schedule_a -from fecfiler.transactions.schedule_a.models import ScheduleA -from fecfiler.transactions.models import Transaction -from fecfiler.reports.models import ReportTransaction import structlog @@ -187,21 +183,3 @@ def test_submit_to_webprint(self): self.assertEqual( webprint_submission.fec_image_url, "https://www.fec.gov/static/img/seal.svg" ) - - -from django.db import connection - - -def bulk_insert(values): - base_sql = "INSERT INTO transactions_transaction (transaction_type_identifier, aggregration_group, _form_type) VALUES" - values_sql = [] - values_data = [] - - for value_list in values: - placeholders = ["%s" for _ in range(len(value_list))] - values_sql.append(f"({', '.join(placeholders)})") - values_data.extend(value_list) - - sql = f"{base_sql} {', '.join(values_sql)}" - with connection.cursor() as cursor: - cursor.executemany(sql, [values_data]) From cce68f4b8849a3717acecfdc7832b2118926c7e1 Mon Sep 17 00:00:00 2001 From: Matt Travers Date: Tue, 16 Jul 2024 15:19:50 -0400 Subject: [PATCH 24/33] Update README.md Updated README to include process to monitor dependency security using Snyk --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7024571e80..cbf628dbcf 100644 --- a/README.md +++ b/README.md @@ -184,4 +184,14 @@ As a best practice policy, please commit any feature code changes made during th ### Google-style inline documentation The project is using the Google Python Style Guide as the baseline to keep code style consistent across project repositories. -See here for comment style rules: https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings \ No newline at end of file +See here for comment style rules: https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings + +### Snyk security scanning +A Snyk online account has been set up for FEC to monitor the FECFile Online GitHub repositories. The management of vulnerability alerts will be handled as a weekly rotating task performed by a developer who will log into the [Snyk Dashboard](https://app.snyk.io/invite/link/accept?invite=93042de6-4eca-4bb5-bf76-9c2e9f895e24&utm_source=link_invite&utm_medium=referral&utm_campaign=product-link-invite&from=link_invite) and perform the following tasks: + +1. Review the vulnerability reports for each of the FECFile Online GitHub repository. +2. Write up a ticket (1 for each reported vulnerability) to remediate the vulnerability. +3. Point and mark each ticket with the following tags: "security", "high priority". +4. Move each new ticket into the current sprint and sprint backlog. + +The weekly assignment log can be found in the Google drive 🔒 [here](https://docs.google.com/spreadsheets/d/1SNMOyGS4JAKgXQ0RhhzoX7M2ib1vm14dD0LxWNpssP4) 🔒 From 1e80c83eb4c361e3cbb799d8d7949d8969900940 Mon Sep 17 00:00:00 2001 From: Matt Travers Date: Tue, 16 Jul 2024 15:38:55 -0400 Subject: [PATCH 25/33] Edit Snyk instructions --- README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index cbf628dbcf..23a59db371 100644 --- a/README.md +++ b/README.md @@ -66,14 +66,12 @@ _Special Note:_ If the fecfile-validate repo was updated, the commit of the upda ### Create a feature branch Using git-flow extensions: -` git flow feature start feature_branch -` +`git flow feature start feature_branch` Without the git-flow extensions: -` git checkout develop +`git checkout develop git pull - git checkout -b feature/feature_branch develop -` + git checkout -b feature/feature_branch develop` - Developer creates a GitHub PR when ready to merge to `develop` branch - Reviewer reviews and merges feature branch into `develop` via GitHub @@ -165,16 +163,19 @@ Set up git secrets to protect oneself from committing sensitive information such - See git-secrets README for more features: https://github.com/awslabs/git-secrets#readme ### Code formatting + [Black](https://github.com/psf/black) is the Python code formatter used on the project. - Install using `pip install black`. - If using vscode, add (or update) the following section of your settings.json to the following so that code is formatted on save: + ``` "[python]": { "editor.defaultFormatter": "ms-python.black-formatter", "editor.formatOnSave": true } ``` + - To format a specific file or directory manually, use `black ` ### Commit local code changes to origin daily @@ -187,11 +188,12 @@ The project is using the Google Python Style Guide as the baseline to keep code See here for comment style rules: https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings ### Snyk security scanning + A Snyk online account has been set up for FEC to monitor the FECFile Online GitHub repositories. The management of vulnerability alerts will be handled as a weekly rotating task performed by a developer who will log into the [Snyk Dashboard](https://app.snyk.io/invite/link/accept?invite=93042de6-4eca-4bb5-bf76-9c2e9f895e24&utm_source=link_invite&utm_medium=referral&utm_campaign=product-link-invite&from=link_invite) and perform the following tasks: 1. Review the vulnerability reports for each of the FECFile Online GitHub repository. -2. Write up a ticket (1 for each reported vulnerability) to remediate the vulnerability. +2. Write up a ticket (1 for each reported "Critical" or "High" severity vulnerability) to remediate the vulnerability. 3. Point and mark each ticket with the following tags: "security", "high priority". 4. Move each new ticket into the current sprint and sprint backlog. -The weekly assignment log can be found in the Google drive 🔒 [here](https://docs.google.com/spreadsheets/d/1SNMOyGS4JAKgXQ0RhhzoX7M2ib1vm14dD0LxWNpssP4) 🔒 +The weekly assignment log can be found in the Google drive 🔒 [here](https://docs.google.com/spreadsheets/d/1SNMOyGS4JAKgXQ0RhhzoX7M2ib1vm14dD0LxWNpssP4) 🔒 From 0d6771ec4e670f93c533f034078dcbd41a1c2845 Mon Sep 17 00:00:00 2001 From: Matt Travers Date: Tue, 16 Jul 2024 16:34:20 -0400 Subject: [PATCH 26/33] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 23a59db371..08db226fce 100644 --- a/README.md +++ b/README.md @@ -195,5 +195,6 @@ A Snyk online account has been set up for FEC to monitor the FECFile Online GitH 2. Write up a ticket (1 for each reported "Critical" or "High" severity vulnerability) to remediate the vulnerability. 3. Point and mark each ticket with the following tags: "security", "high priority". 4. Move each new ticket into the current sprint and sprint backlog. +5. Update weekly assignment log with tickets created or "None". The weekly assignment log can be found in the Google drive 🔒 [here](https://docs.google.com/spreadsheets/d/1SNMOyGS4JAKgXQ0RhhzoX7M2ib1vm14dD0LxWNpssP4) 🔒 From 338c89549a42a74da0ef7ce2d4e85fc294906fc7 Mon Sep 17 00:00:00 2001 From: Matt Travers Date: Wed, 17 Jul 2024 12:01:30 -0400 Subject: [PATCH 27/33] Add comments to top of script --- .../bulk-testing-data-fixture-generator.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/performance-testing/bulk-testing-data-fixture-generator.py b/performance-testing/bulk-testing-data-fixture-generator.py index 5609bbaa3f..d5ea153527 100644 --- a/performance-testing/bulk-testing-data-fixture-generator.py +++ b/performance-testing/bulk-testing-data-fixture-generator.py @@ -1,3 +1,28 @@ +"""Creating and loading bulk data with fixtures + +Fixtures are .json files that can be used to load data into the database. +Loading data with fixtures is far faster than creating records with individual +requests, making it especially useful for preparing a database for ad-hoc +performance testing. + +A script has been provided for generating fixtures with specific numbers of +records. You can run the script with: + + python bulk-testing-data-fixture-generator.py + +Running the script with the `-h` or `--help` flags will provide additional +information. + +Once you have a fixture, you can load it into the database by performing the +following steps: + +1. Enter a fecfile-api docker container +- (For Local) Use `docker exec -it fecfile-api /bin/bash` +- (For Cloud.gov or Circle CI) ssh into your docker instance of choice. +2. (Cloud.gov only) use `/tmp/lifecycle/shell` to establish a shell session. +3. Run `python manage.py loaddata FIXTURE-NAME` to load your fixture. +""" + import json from random import choice, choices, randrange import string From ea0c49a6e18abd672d62a3c1a3669fbb422162ca Mon Sep 17 00:00:00 2001 From: Sasha Dresden Date: Wed, 17 Jul 2024 15:07:05 -0400 Subject: [PATCH 28/33] Pin authlib to 1.3.1 for security --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f9fe1a58c6..0654b27cc3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,4 +29,5 @@ django-structlog[celery]==7.1.0 rich==13.7.0 # Pinning sqlparse to 0.5.0 as it is a sub-dependency that needs to be upgraded for security -sqlparse==0.5.0 \ No newline at end of file +sqlparse==0.5.0 +authlib==1.3.1 \ No newline at end of file From 32ba630b7caff74b3d194c3711b5f5bdc62916b0 Mon Sep 17 00:00:00 2001 From: Sasha Dresden Date: Wed, 17 Jul 2024 15:18:18 -0400 Subject: [PATCH 29/33] Pin setuptools to 70.3.0 for security --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f9fe1a58c6..ed90195fee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,4 +29,5 @@ django-structlog[celery]==7.1.0 rich==13.7.0 # Pinning sqlparse to 0.5.0 as it is a sub-dependency that needs to be upgraded for security -sqlparse==0.5.0 \ No newline at end of file +sqlparse==0.5.0 +setuptools==70.3.0 \ No newline at end of file From 34d43ea2765a81377d148f927a4f15e51573b804 Mon Sep 17 00:00:00 2001 From: Matt Travers Date: Wed, 17 Jul 2024 16:14:13 -0400 Subject: [PATCH 30/33] Updated gh-pages package --- package-lock.json | 803 ++++++++++++++-------------------------------- package.json | 2 +- 2 files changed, 244 insertions(+), 561 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1c51018809..31c764a8ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,91 +1,107 @@ { "name": "fecfile-web-api", "version": "0.0.1", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "fecfile-web-api", "version": "0.0.1", "dependencies": { - "gh-pages": "^3.2.3" + "gh-pages": "6.1.1" } }, - "node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", "dependencies": { - "lodash": "^4.17.14" + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/async/node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" }, - "node_modules/email-addresses": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", - "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==" + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, - "node_modules/filenamify": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", - "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dependencies": { - "filename-reserved-regex": "^2.0.0", - "strip-outer": "^1.0.1", - "trim-repeated": "^1.0.0" - }, + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=16" } }, - "node_modules/filenamify/node_modules/escape-string-regexp": { + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/email-addresses": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-5.0.0.tgz", + "integrity": "sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==" + }, + "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": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "engines": { "node": ">=0.8.0" } }, - "node_modules/filenamify/node_modules/filename-reserved-regex": { + "node_modules/filename-reserved-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", - "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", "engines": { "node": ">=4" } }, - "node_modules/filenamify/node_modules/strip-outer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "node_modules/filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", "dependencies": { - "escape-string-regexp": "^1.0.2" + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/filenamify/node_modules/trim-repeated": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", - "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", - "dependencies": { - "escape-string-regexp": "^1.0.2" + "node": ">=8" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/find-cache-dir": { @@ -104,12 +120,7 @@ "url": "https://github.com/avajs/find-cache-dir?sponsor=1" } }, - "node_modules/find-cache-dir/node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" - }, - "node_modules/find-cache-dir/node_modules/find-up": { + "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", @@ -121,136 +132,35 @@ "node": ">=8" } }, - "node_modules/find-cache-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-cache-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-cache-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", "dependencies": { "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/fs-extra/node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "node_modules/fs-extra/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "node": ">=14.14" } }, - "node_modules/fs-extra/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "engines": { - "node": ">= 4.0.0" - } + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/gh-pages": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-3.2.3.tgz", - "integrity": "sha512-jA1PbapQ1jqzacECfjUaO9gV8uBgU6XNMV0oXLtfCX3haGLe5Atq8BxlrADhbD6/UdG9j6tZLWAkAybndOXTJg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-6.1.1.tgz", + "integrity": "sha512-upnohfjBwN5hBP9w2dPE7HO5JJTHzSGMV1JrLrHvNuqmjoYHg6TBrCcnEoorjG/e0ejbuvnwyKMdTyM40PEByw==", "dependencies": { - "async": "^2.6.1", - "commander": "^2.18.0", - "email-addresses": "^3.0.1", + "async": "^3.2.4", + "commander": "^11.0.0", + "email-addresses": "^5.0.0", "filenamify": "^4.3.0", "find-cache-dir": "^3.3.1", - "fs-extra": "^8.1.0", + "fs-extra": "^11.1.1", "globby": "^6.1.0" }, "bin": { @@ -261,10 +171,30 @@ "node": ">=10" } }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/globby": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", "dependencies": { "array-union": "^1.0.1", "glob": "^7.0.3", @@ -276,83 +206,63 @@ "node": ">=0.10.0" } }, - "node_modules/globby/node_modules/array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dependencies": { - "array-uniq": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, - "node_modules/globby/node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "engines": { - "node": ">=0.10.0" + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/globby/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "node_modules/globby/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/globby/node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "node_modules/globby/node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "node_modules/globby/node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "p-locate": "^4.1.0" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=8" } }, - "node_modules/globby/node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/globby/node_modules/minimatch": { + "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", @@ -363,50 +273,91 @@ "node": "*" } }, - "node_modules/globby/node_modules/object-assign": { + "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "engines": { "node": ">=0.10.0" } }, - "node_modules/globby/node_modules/once": { + "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dependencies": { "wrappy": "1" } }, - "node_modules/globby/node_modules/path-is-absolute": { + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "engines": { "node": ">=0.10.0" } }, - "node_modules/globby/node_modules/pify": { + "node_modules/pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "engines": { "node": ">=0.10.0" } }, - "node_modules/globby/node_modules/pinkie": { + "node_modules/pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", "engines": { "node": ">=0.10.0" } }, - "node_modules/globby/node_modules/pinkie-promise": { + "node_modules/pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", "dependencies": { "pinkie": "^2.0.0" }, @@ -414,327 +365,59 @@ "node": ">=0.10.0" } }, - "node_modules/globby/node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - } - }, - "dependencies": { - "async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "requires": { - "lodash": "^4.17.14" - }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dependencies": { - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - } + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "email-addresses": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", - "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==" + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } }, - "filenamify": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", - "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", - "requires": { - "filename-reserved-regex": "^2.0.0", - "strip-outer": "^1.0.1", - "trim-repeated": "^1.0.0" - }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", "dependencies": { - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "filename-reserved-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", - "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=" - }, - "strip-outer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", - "requires": { - "escape-string-regexp": "^1.0.2" - } - }, - "trim-repeated": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", - "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", - "requires": { - "escape-string-regexp": "^1.0.2" - } - } - } - }, - "find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" + "escape-string-regexp": "^1.0.2" }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", "dependencies": { - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "requires": { - "semver": "^6.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "requires": { - "find-up": "^4.0.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "escape-string-regexp": "^1.0.2" }, - "dependencies": { - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" - } - } - }, - "gh-pages": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-3.2.3.tgz", - "integrity": "sha512-jA1PbapQ1jqzacECfjUaO9gV8uBgU6XNMV0oXLtfCX3haGLe5Atq8BxlrADhbD6/UdG9j6tZLWAkAybndOXTJg==", - "requires": { - "async": "^2.6.1", - "commander": "^2.18.0", - "email-addresses": "^3.0.1", - "filenamify": "^4.3.0", - "find-cache-dir": "^3.3.1", - "fs-extra": "^8.1.0", - "globby": "^6.1.0" + "engines": { + "node": ">=0.10.0" } }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "requires": { - "array-uniq": "^1.0.1" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "requires": { - "pinkie": "^2.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - } + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" } } } diff --git a/package.json b/package.json index 4ac7de1ec7..8a77d465f8 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,6 @@ "name": "fecfile-web-api", "version": "0.0.1", "dependencies": { - "gh-pages": "^3.2.3" + "gh-pages": "6.1.1" } } From 41e28a9027c5ceb3dfd9c9e13824dbd6062f98c3 Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Wed, 17 Jul 2024 16:42:06 -0400 Subject: [PATCH 31/33] Updates the script to use only one environment variable, to include instructions for using that variable, and to include an override. A warning will be issued if neither the environment variable nor the override are used. --- performance-testing/README.md | 7 ++- .../bulk-testing-data-fixture-generator.py | 52 +++++++++++++------ 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/performance-testing/README.md b/performance-testing/README.md index d5d14131c6..cc3e2a15d8 100644 --- a/performance-testing/README.md +++ b/performance-testing/README.md @@ -128,9 +128,14 @@ A script has been provided for generating fixtures with specific numbers of reco ``` python bulk-testing-data-fixture-generator.py ``` +The script requires an environment variable to function: +- `LOCAL_TEST_COMMITTEE_UUID`: Used to ensure that created records are viewable within the test committee. +For most cases, the value in the `e2e-test-data.json` fixture is what you're looking for. This can be overriden +by using the `--committee-uuid` optional parameter when running the script. + Running the script with the `-h` or `--help` flags will provide additional information. -Once you have a fixture, you can load it into the database by performing the following steps: +Once you have a fixture, you can load it into the database by following these steps: 1. Enter a fecfile-api docker container - (For Local) Use `docker exec -it fecfile-api /bin/bash` diff --git a/performance-testing/bulk-testing-data-fixture-generator.py b/performance-testing/bulk-testing-data-fixture-generator.py index d5ea153527..5c581c5a03 100644 --- a/performance-testing/bulk-testing-data-fixture-generator.py +++ b/performance-testing/bulk-testing-data-fixture-generator.py @@ -1,20 +1,23 @@ """Creating and loading bulk data with fixtures -Fixtures are .json files that can be used to load data into the database. -Loading data with fixtures is far faster than creating records with individual -requests, making it especially useful for preparing a database for ad-hoc -performance testing. +Fixtures are .json files that can be used to load data into the database. Loading data +with fixtures is far faster than creating records with individual requests, making it +especially useful for preparing a database for ad-hoc performance testing. -A script has been provided for generating fixtures with specific numbers of -records. You can run the script with: +A script has been provided for generating fixtures with specific numbers of records. +You can run the script with: +``` + python bulk-testing-data-fixture-generator.py +``` +The script requires an environment variable to function: +- `LOCAL_TEST_COMMITTEE_UUID`: Used to ensure that created records are viewable within +the test committee. For most cases, the value in the `e2e-test-data.json` fixture is +what you're looking for. This can be overriden by using the `--committee-uuid` optional +parameter when running the script. - python bulk-testing-data-fixture-generator.py +Running the script with the `-h` or `--help` flags will provide additional information. -Running the script with the `-h` or `--help` flags will provide additional -information. - -Once you have a fixture, you can load it into the database by performing the -following steps: +Once you have a fixture, you can load it into the database by following these steps: 1. Enter a fecfile-api docker container - (For Local) Use `docker exec -it fecfile-api /bin/bash` @@ -30,8 +33,7 @@ from uuid import UUID, uuid4 -PRIMARY_COMMITTEE_FEC_ID = os.environ.get("LOCAL_TEST_USER", "C00000000") -PRIMARY_COMMITTEE_UUID = os.environ.get("LOCAL_TEST_COMMITTEE_UUID", uuid4()) +PRIMARY_COMMITTEE_UUID = os.environ.get("LOCAL_TEST_COMMITTEE_UUID", None) CONTACT_TYPES = ["IND", "ORG", "COM", "CAN"] SCHEDULE_FORMATS = { "A": { @@ -368,7 +370,7 @@ def create_transaction(committee_uuid, contact_id, report): def create_records(transaction_count, report_count, contact_count, committee_count): - committees = [create_committee(PRIMARY_COMMITTEE_FEC_ID, PRIMARY_COMMITTEE_UUID)] + committees = [create_committee("N/A", PRIMARY_COMMITTEE_UUID)] for _ in range(max(committee_count, 0)): committees.append(create_committee()) @@ -412,7 +414,7 @@ def create_records(transaction_count, report_count, contact_count, committee_cou def prepare_records(records): out_records = [] for c in records.values(): - if c["committee_record"]["fields"]["committee_id"] != PRIMARY_COMMITTEE_FEC_ID: + if c["committee_record"]["fields"]["committee_id"] != "N/A": out_records.append(c["committee_record"]) for c in records.values(): @@ -491,8 +493,26 @@ def create_fixture(transactions=1000, reports=5, contacts=100, committees=1): Transactions are comprised of three records each. Defaults to one thousand (1,000).""" ) + parser.add_argument( + '--committee-uuid', + default=None, + help="""Manually specify the Committee Account UUID used in created records. + This overrides the UUID found in the `LOCAL_TEST_COMMITTEE_UUID` + environment variable.""" + ) args = parser.parse_args() + if args.committee_uuid is not None: + PRIMARY_COMMITTEE_UUID = args.committee_uuid + + if PRIMARY_COMMITTEE_UUID is None: + print( + "\nPlease provide a Committee Account UUID either with the " + + "`LOCAL_TEST_COMMITTEE_UUID` environment variable or the --committee-uuid " + + "optional parameter.\n" + ) + exit() + sorted_records = create_fixture( args.transactions, args.reports, From f4b5b16773a84db4bbbdf8488f9d629261c84779 Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Wed, 17 Jul 2024 16:46:49 -0400 Subject: [PATCH 32/33] Linting fix --- performance-testing/bulk-testing-data-fixture-generator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/performance-testing/bulk-testing-data-fixture-generator.py b/performance-testing/bulk-testing-data-fixture-generator.py index 5c581c5a03..8347ef56c0 100644 --- a/performance-testing/bulk-testing-data-fixture-generator.py +++ b/performance-testing/bulk-testing-data-fixture-generator.py @@ -507,9 +507,9 @@ def create_fixture(transactions=1000, reports=5, contacts=100, committees=1): if PRIMARY_COMMITTEE_UUID is None: print( - "\nPlease provide a Committee Account UUID either with the " + - "`LOCAL_TEST_COMMITTEE_UUID` environment variable or the --committee-uuid " + - "optional parameter.\n" + "\nPlease provide a Committee Account UUID either with the " + + "`LOCAL_TEST_COMMITTEE_UUID` environment variable or the --committee-uuid " + + "optional parameter.\n" ) exit() From 37e66a1f676d873364701affe0dd45a628462488 Mon Sep 17 00:00:00 2001 From: Sasha Dresden Date: Thu, 18 Jul 2024 13:40:12 -0400 Subject: [PATCH 33/33] Performance improvement for aggregate trigger --- ...10_update_aggregate_trigger_performance.py | 205 ++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 django-backend/fecfiler/transactions/migrations/0010_update_aggregate_trigger_performance.py diff --git a/django-backend/fecfiler/transactions/migrations/0010_update_aggregate_trigger_performance.py b/django-backend/fecfiler/transactions/migrations/0010_update_aggregate_trigger_performance.py new file mode 100644 index 0000000000..dba8033a4a --- /dev/null +++ b/django-backend/fecfiler/transactions/migrations/0010_update_aggregate_trigger_performance.py @@ -0,0 +1,205 @@ +# Generated by Django 4.2.11 on 2024-07-17 19:59 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("transactions", "0009_update_calculate_loan_payment_to_date"), + ] + + operations = [ + migrations.RunSQL( + """ + CREATE OR REPLACE FUNCTION calculate_entity_aggregates( + txn RECORD, sql_committee_id text + ) + RETURNS VOID AS $$ + DECLARE + schedule_date date; + BEGIN + IF txn.schedule_a_id IS NOT NULL THEN + SELECT + contribution_date INTO schedule_date + FROM + transactions_schedulea + WHERE + id = txn.schedule_a_id; + ELSIF txn.schedule_b_id IS NOT NULL THEN + SELECT + expenditure_date INTO schedule_date + FROM + transactions_scheduleb + WHERE + id = txn.schedule_b_id; + END IF; + + EXECUTE ' + UPDATE transactions_transaction AS t + SET aggregate = tc.new_sum + FROM ( + SELECT + id, + aggregate, + date, + SUM(effective_amount) OVER (ORDER BY date, created) + AS new_sum + FROM transaction_view__' || sql_committee_id || ' + WHERE + contact_1_id = $1 + AND EXTRACT(YEAR FROM date) = $2 + AND aggregation_group = $3 + AND force_unaggregated IS NOT TRUE + ) AS tc + WHERE t.id = tc.id + AND ( + tc.date > $4 + OR ( + tc.date = $4 + AND t.created >= $5 + ) + ); + ' + USING + txn.contact_1_id, + EXTRACT(YEAR FROM schedule_date), + txn.aggregation_group, + schedule_date, + txn.created; + END; + $$ + LANGUAGE plpgsql; + """ + ), + migrations.RunSQL( + """ + CREATE OR REPLACE FUNCTION calculate_calendar_ytd_per_election_office( + txn RECORD, sql_committee_id text + ) + RETURNS VOID + AS $$ + DECLARE + schedule_date date; + v_election_code text; + v_candidate_office text; + v_candidate_state text; + v_candidate_district text; + BEGIN + SELECT + COALESCE(disbursement_date, dissemination_date) INTO schedule_date + FROM transactions_schedulee + WHERE id = txn.schedule_e_id; + + SELECT election_code INTO v_election_code + FROM transactions_schedulee + WHERE id = txn.schedule_e_id; + + SELECT + candidate_office, + candidate_state, + candidate_district INTO v_candidate_office, + v_candidate_state, + v_candidate_district + FROM contacts + WHERE id = txn.contact_2_id; + EXECUTE ' + UPDATE transactions_transaction AS t + SET _calendar_ytd_per_election_office = tc.new_sum + FROM ( + SELECT + t.id, + t.date, + SUM(t.effective_amount) OVER + (ORDER BY t.date, t.created) AS new_sum + FROM transactions_schedulee e + JOIN transaction_view__' || sql_committee_id || ' t + ON e.id = t.schedule_e_id + JOIN contacts c + ON t.contact_2_id = c.id + WHERE + e.election_code = $1 + AND c.candidate_office = $2 + AND ( + c.candidate_state = $3 + OR ( + c.candidate_state IS NULL + AND $3 = '''' + ) + ) + AND ( + c.candidate_district = $4 + OR ( + c.candidate_district IS NULL + AND $4 = '''' + ) + ) + AND EXTRACT(YEAR FROM t.date) = $5 + AND aggregation_group = $6 + AND force_unaggregated IS NOT TRUE + ) AS tc + WHERE t.id = tc.id + AND ( + tc.date > $7 + OR ( + tc.date = $7 + AND t.created >= $8 + ) + ); + ' + USING + v_election_code, + v_candidate_office, + COALESCE(v_candidate_state, ''), + COALESCE(v_candidate_district, ''), + EXTRACT(YEAR FROM schedule_date), + txn.aggregation_group, + schedule_date, + txn.created; + END; + $$ + LANGUAGE plpgsql; + """ + ), + migrations.RunSQL( + """ + CREATE OR REPLACE FUNCTION calculate_loan_payment_to_date( + txn RECORD, sql_committee_id text + ) + RETURNS VOID + AS $$ + BEGIN + EXECUTE ' + UPDATE transactions_transaction AS t + SET loan_payment_to_date = tc.new_sum + FROM ( + SELECT + id, + loan_key, + SUM(effective_amount) OVER (ORDER BY loan_key) AS new_sum + FROM transaction_view__' || sql_committee_id || ' + WHERE loan_key LIKE ( + SELECT + CASE + WHEN loan_id IS NULL THEN transaction_id + ELSE ( + SELECT transaction_id + FROM transactions_transaction + WHERE id = t.loan_id + ) + END + FROM transactions_transaction t + WHERE id = $1 + ) || ''%%'' -- Match the loan_key with a transaction_id prefix + ) AS tc + WHERE t.id = tc.id + AND tc.loan_key LIKE ''%%LOAN'' + ; + ' + USING txn.id; + END; + $$ + LANGUAGE plpgsql; + """ + ), + ]