From 31295053f91ae916dc957ebba9c082fae8a515f1 Mon Sep 17 00:00:00 2001 From: rachidatecs Date: Wed, 7 Jun 2023 13:19:22 -0400 Subject: [PATCH 01/26] Implement 'investigating' email templates and tigger email send on admin domain application save, status change to investidated. --- src/registrar/admin.py | 45 ++++++- .../emails/status_change_in_review.txt | 81 ++++++++++++ .../status_change_in_review_subject.txt | 1 + src/registrar/tests/test_admin.py | 120 ++++++++++++++++++ 4 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 src/registrar/templates/emails/status_change_in_review.txt create mode 100644 src/registrar/templates/emails/status_change_in_review_subject.txt create mode 100644 src/registrar/tests/test_admin.py diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 439dfd9f9..7fafb730c 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1,11 +1,14 @@ +import logging from django.contrib import admin from django.contrib.auth.admin import UserAdmin from django.contrib.contenttypes.models import ContentType from django.http.response import HttpResponseRedirect from django.urls import reverse - +from .utility.email import send_templated_email, EmailSendingError from . import models +logger = logging.getLogger(__name__) + class AuditedAdmin(admin.ModelAdmin): @@ -50,13 +53,51 @@ class MyHostAdmin(AuditedAdmin): inlines = [HostIPInline] +class DomainApplicationAdmin(AuditedAdmin): + + """Customize the applications listing view.""" + + # Trigger action when a fieldset is changed + def save_model(self, request, obj, form, change): + if change: # Check if the application is being edited + # Get the original application from the database + original_obj = models.DomainApplication.objects.get(pk=obj.pk) + + if obj.status != original_obj.status and obj.status == "investigating": + if ( + original_obj.submitter is None + or original_obj.submitter.email is None + ): + logger.warning( + "Cannot send status change (in review) email," + "no submitter email address." + ) + return + try: + print( + f"original_obj.submitter.email {original_obj.submitter.email}" + ) + send_templated_email( + "emails/status_change_in_review.txt", + "emails/status_change_in_review_subject.txt", + original_obj.submitter.email, + context={"application": obj}, + ) + except EmailSendingError: + logger.warning( + "Failed to send status change (in review) email", exc_info=True + ) + + super().save_model(request, obj, form, change) + + admin.site.register(models.User, MyUserAdmin) admin.site.register(models.UserDomainRole, AuditedAdmin) admin.site.register(models.Contact, AuditedAdmin) admin.site.register(models.DomainInvitation, AuditedAdmin) -admin.site.register(models.DomainApplication, AuditedAdmin) admin.site.register(models.DomainInformation, AuditedAdmin) admin.site.register(models.Domain, AuditedAdmin) admin.site.register(models.Host, MyHostAdmin) admin.site.register(models.Nameserver, MyHostAdmin) admin.site.register(models.Website, AuditedAdmin) +admin.site.register(models.DomainApplication, DomainApplicationAdmin) diff --git a/src/registrar/templates/emails/status_change_in_review.txt b/src/registrar/templates/emails/status_change_in_review.txt new file mode 100644 index 000000000..9b7928502 --- /dev/null +++ b/src/registrar/templates/emails/status_change_in_review.txt @@ -0,0 +1,81 @@ +{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #} +Hi {{ application.submitter.first_name }}. + +Your .gov domain request is being reviewed. + +DOMAIN REQUESTED: {{ application.requested_domain.name }} +REQUEST RECEIVED ON: {{ application.updated_at|date }} +REQUEST #: {{ application.id }} +STATUS: In review + + +NEED TO MAKE CHANGES? + +If you need to change your request you have to first withdraw it. Once you +withdraw the request you can edit it and submit it again. Changing your request +might add to the wait time. Learn more about withdrawing your request. +. + + +NEXT STEPS + +- We’re reviewing your request. This usually takes 20 business days. + +- You can check the status of your request at any time. + + +- We’ll email you with questions or when we complete our review. + + +THANK YOU + +.Gov helps the public identify official, trusted information. Thank you for +requesting a .gov domain. + +---------------------------------------------------------------- + +SUMMARY OF YOUR DOMAIN REQUEST + +Type of organization: +{{ application.get_organization_type_display }} + +Organization name and mailing address: +{% spaceless %}{{ application.organization_name }} +{{ application.address_line1 }}{% if application.address_line2 %} +{{ application.address_line2 }}{% endif %} +{{ application.city }}, {{ application.state_territory }} +{{ application.zipcode }}{% if application.urbanization %} +{{ application.urbanization }}{% endif %}{% endspaceless %} +{% if application.type_of_work %}{# if block makes one newline if it's false #} +Type of work: +{% spaceless %}{{ application.type_of_work }}{% endspaceless %} +{% endif %} +Authorizing official: +{% spaceless %}{% include "emails/includes/contact.txt" with contact=application.authorizing_official %}{% endspaceless %} +{% if application.current_websites.exists %}{# if block makes a newline #} +Current website for your organization: {% for site in application.current_websites.all %} +{% spaceless %}{{ site.website }}{% endspaceless %} +{% endfor %}{% endif %} +.gov domain: +{{ application.requested_domain.name }} +{% for site in application.alternative_domains.all %}{% spaceless %}{{ site.website }}{% endspaceless %} +{% endfor %} +Purpose of your domain: +{{ application.purpose }} + +Your contact information: +{% spaceless %}{% include "emails/includes/contact.txt" with contact=application.submitter %}{% endspaceless %} +{% if application.other_contacts.all %} +Other employees from your organization: +{% for other in application.other_contacts.all %} +{% spaceless %}{% include "emails/includes/contact.txt" with contact=other %}{% endspaceless %} +{% endfor %}{% endif %}{% if application.anything_else %} +Anything else we should know? +{{ application.anything_else }} +{% endif %} +---------------------------------------------------------------- + +The .gov team +Contact us: +Visit +{% endautoescape %} diff --git a/src/registrar/templates/emails/status_change_in_review_subject.txt b/src/registrar/templates/emails/status_change_in_review_subject.txt new file mode 100644 index 000000000..e4e43138b --- /dev/null +++ b/src/registrar/templates/emails/status_change_in_review_subject.txt @@ -0,0 +1 @@ +Your .gov domain request is being reviewed \ No newline at end of file diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py new file mode 100644 index 000000000..35684ab00 --- /dev/null +++ b/src/registrar/tests/test_admin.py @@ -0,0 +1,120 @@ +from django.test import TestCase, RequestFactory +from django.contrib.admin.sites import AdminSite +from registrar.admin import DomainApplicationAdmin +from django.contrib.auth import get_user_model +from registrar.models import Contact, DraftDomain, Website, DomainApplication, User + +from django.conf import settings +from unittest.mock import MagicMock, ANY +import boto3_mocking # type: ignore + + +class TestDomainApplicationAdmin(TestCase): + def setUp(self): + self.site = AdminSite() + self.factory = RequestFactory() + + def _completed_application( + self, + has_other_contacts=True, + has_current_website=True, + has_alternative_gov_domain=True, + has_type_of_work=True, + has_anything_else=True, + ): + """A completed domain application.""" + user = get_user_model().objects.create(username="username") + ao, _ = Contact.objects.get_or_create( + first_name="Testy", + last_name="Tester", + title="Chief Tester", + email="testy@town.com", + phone="(555) 555 5555", + ) + domain, _ = DraftDomain.objects.get_or_create(name="city.gov") + alt, _ = Website.objects.get_or_create(website="city1.gov") + current, _ = Website.objects.get_or_create(website="city.com") + you, _ = Contact.objects.get_or_create( + first_name="Testy you", + last_name="Tester you", + title="Admin Tester", + email="mayor@igorville.gov", + phone="(555) 555 5556", + ) + other, _ = Contact.objects.get_or_create( + first_name="Testy2", + last_name="Tester2", + title="Another Tester", + email="testy2@town.com", + phone="(555) 555 5557", + ) + domain_application_kwargs = dict( + organization_type="federal", + federal_type="executive", + purpose="Purpose of the site", + is_policy_acknowledged=True, + organization_name="Testorg", + address_line1="address 1", + address_line2="address 2", + state_territory="NY", + zipcode="10002", + authorizing_official=ao, + requested_domain=domain, + submitter=you, + creator=user, + ) + if has_type_of_work: + domain_application_kwargs["type_of_work"] = "e-Government" + if has_anything_else: + domain_application_kwargs["anything_else"] = "There is more" + + application, _ = DomainApplication.objects.get_or_create( + **domain_application_kwargs + ) + + if has_other_contacts: + application.other_contacts.add(other) + if has_current_website: + application.current_websites.add(current) + if has_alternative_gov_domain: + application.alternative_domains.add(alt) + + return application + + @boto3_mocking.patching + def test_save_model_sends_email_on_property_change(self): + # make sure there is no user with this email + EMAIL = "mayor@igorville.gov" + User.objects.filter(email=EMAIL).delete() + + mock_client = MagicMock() + mock_client_instance = mock_client.return_value + + with boto3_mocking.clients.handler_for("sesv2", mock_client): + # Create a sample application + application = self._completed_application() + + # Create a mock request + request = self.factory.post( + "/admin/registrar/domainapplication/{}/change/".format(application.pk) + ) + + # Create an instance of the model admin + model_admin = DomainApplicationAdmin(DomainApplication, self.site) + + # Modify the application's property + application.status = "investigating" + + # Use the model admin's save_model method + model_admin.save_model(request, application, form=None, change=True) + + # Assert that the email was sent + + mock_client_instance.send_email.assert_called_once_with( + FromEmailAddress=settings.DEFAULT_FROM_EMAIL, + Destination={"ToAddresses": [EMAIL]}, + Content=ANY, + ) + + # Cleanup + application.delete() From efc800a3bf9e2fd02acd53c4212ea61e09f03062 Mon Sep 17 00:00:00 2001 From: rachidatecs Date: Wed, 7 Jun 2023 14:26:44 -0400 Subject: [PATCH 02/26] Personalize the unit test to look for the 'in review' copy in th email's body --- src/registrar/tests/test_admin.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 35684ab00..19f16cfe0 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -107,14 +107,25 @@ class TestDomainApplicationAdmin(TestCase): # Use the model admin's save_model method model_admin.save_model(request, application, form=None, change=True) + + # Access the arguments passed to send_email + call_args = mock_client_instance.send_email.call_args + args, kwargs = call_args - # Assert that the email was sent + # Retrieve the email details from the arguments + from_email = kwargs.get("FromEmailAddress") + to_email = kwargs["Destination"]["ToAddresses"][0] + email_content = kwargs["Content"] + email_body = email_content['Simple']['Body']['Text']['Data'] - mock_client_instance.send_email.assert_called_once_with( - FromEmailAddress=settings.DEFAULT_FROM_EMAIL, - Destination={"ToAddresses": [EMAIL]}, - Content=ANY, - ) + # Assert or perform other checks on the email details + expected_string = "Your .gov domain request is being reviewed" + assert from_email == settings.DEFAULT_FROM_EMAIL + assert to_email == EMAIL + assert expected_string in email_body + + # Perform assertions on the mock call itself + mock_client_instance.send_email.assert_called_once() # Cleanup application.delete() From 809c752397a10bff08b1f855c1673e8c528f1017 Mon Sep 17 00:00:00 2001 From: rachidatecs Date: Wed, 7 Jun 2023 14:50:38 -0400 Subject: [PATCH 03/26] lint --- src/registrar/tests/test_admin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 19f16cfe0..1c63296e0 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -5,7 +5,7 @@ from django.contrib.auth import get_user_model from registrar.models import Contact, DraftDomain, Website, DomainApplication, User from django.conf import settings -from unittest.mock import MagicMock, ANY +from unittest.mock import MagicMock import boto3_mocking # type: ignore @@ -107,7 +107,7 @@ class TestDomainApplicationAdmin(TestCase): # Use the model admin's save_model method model_admin.save_model(request, application, form=None, change=True) - + # Access the arguments passed to send_email call_args = mock_client_instance.send_email.call_args args, kwargs = call_args @@ -116,7 +116,7 @@ class TestDomainApplicationAdmin(TestCase): from_email = kwargs.get("FromEmailAddress") to_email = kwargs["Destination"]["ToAddresses"][0] email_content = kwargs["Content"] - email_body = email_content['Simple']['Body']['Text']['Data'] + email_body = email_content["Simple"]["Body"]["Text"]["Data"] # Assert or perform other checks on the email details expected_string = "Your .gov domain request is being reviewed" From 54efe9ee32a3ccb99e93f2e95ea20d8af5585da1 Mon Sep 17 00:00:00 2001 From: rachidatecs Date: Thu, 8 Jun 2023 22:25:47 -0400 Subject: [PATCH 04/26] change asserts to self.assert --- src/registrar/tests/test_admin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 1c63296e0..44739fd73 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -120,9 +120,9 @@ class TestDomainApplicationAdmin(TestCase): # Assert or perform other checks on the email details expected_string = "Your .gov domain request is being reviewed" - assert from_email == settings.DEFAULT_FROM_EMAIL - assert to_email == EMAIL - assert expected_string in email_body + self.assertEqual(from_email, settings.DEFAULT_FROM_EMAIL) + self.assertEqual(to_email, EMAIL) + self.assertIn(expected_string, email_body) # Perform assertions on the mock call itself mock_client_instance.send_email.assert_called_once() From ed53ef3400f1ecd0a6f3c8de47d3dc68460eb6fa Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Mon, 12 Jun 2023 12:17:10 -0700 Subject: [PATCH 05/26] Add new developer sandbox 'rb' infrastructure --- .github/workflows/migrate.yaml | 1 + .github/workflows/reset-db.yaml | 1 + ops/manifests/manifest-rb.yaml | 29 +++++++++++++++++++++++++++++ src/registrar/config/settings.py | 1 + 4 files changed, 32 insertions(+) create mode 100644 ops/manifests/manifest-rb.yaml diff --git a/.github/workflows/migrate.yaml b/.github/workflows/migrate.yaml index 820c219fd..54ae2c17c 100644 --- a/.github/workflows/migrate.yaml +++ b/.github/workflows/migrate.yaml @@ -14,6 +14,7 @@ on: description: Which environment should we run migrations for? options: - stable + - rb - ab - bl - rjm diff --git a/.github/workflows/reset-db.yaml b/.github/workflows/reset-db.yaml index 5d863e6d6..ee53b0bd4 100644 --- a/.github/workflows/reset-db.yaml +++ b/.github/workflows/reset-db.yaml @@ -15,6 +15,7 @@ on: description: Which environment should we flush and re-load data for? options: - stable + - rb - ab - bl - rjm diff --git a/ops/manifests/manifest-rb.yaml b/ops/manifests/manifest-rb.yaml new file mode 100644 index 000000000..b228980e0 --- /dev/null +++ b/ops/manifests/manifest-rb.yaml @@ -0,0 +1,29 @@ +--- +applications: +- name: getgov-rb + buildpacks: + - python_buildpack + path: ../../src + instances: 1 + memory: 512M + stack: cflinuxfs4 + timeout: 180 + command: ./run.sh + health-check-type: http + health-check-http-endpoint: /health + env: + # Send stdout and stderr straight to the terminal without buffering + PYTHONUNBUFFERED: yup + # Tell Django where to find its configuration + DJANGO_SETTINGS_MODULE: registrar.config.settings + # Tell Django where it is being hosted + DJANGO_BASE_URL: https://getgov-rb.app.cloud.gov + # Tell Django how much stuff to log + DJANGO_LOG_LEVEL: INFO + # default public site location + GETGOV_PUBLIC_SITE_URL: https://beta.get.gov + routes: + - route: getgov-rb.app.cloud.gov + services: + - getgov-credentials + - getgov-rb-database diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index b8b32df41..efa3db4a4 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -564,6 +564,7 @@ SECURE_SSL_REDIRECT = True # web server configurations. ALLOWED_HOSTS = [ "getgov-stable.app.cloud.gov", + "getgov-rb.app.cloud.gov", "getgov-ab.app.cloud.gov", "getgov-bl.app.cloud.gov", "getgov-rjm.app.cloud.gov", From f3bf3f9e661648f70680515a7a9b58c1f0de3135 Mon Sep 17 00:00:00 2001 From: rachidatecs Date: Mon, 12 Jun 2023 17:29:30 -0400 Subject: [PATCH 06/26] Add logging, templatize SUMMARY in email templates, templatize complete_application in tests, to integrate FSM inspect the status change that was made in the admin view here and call the transition method that's now defined in the model --- src/registrar/admin.py | 35 ++----- src/registrar/models/domain_application.py | 37 ++++++++ .../emails/includes/application_summary.txt | 39 ++++++++ .../emails/status_change_in_review.txt | 40 +------- .../emails/submission_confirmation.txt | 40 +------- src/registrar/tests/common.py | 73 +++++++++++++++ src/registrar/tests/test_admin.py | 73 +-------------- src/registrar/tests/test_emails.py | 92 +++---------------- src/registrar/tests/test_views.py | 90 +++--------------- 9 files changed, 190 insertions(+), 329 deletions(-) create mode 100644 src/registrar/templates/emails/includes/application_summary.txt diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 7fafb730c..957a51867 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -4,7 +4,6 @@ from django.contrib.auth.admin import UserAdmin from django.contrib.contenttypes.models import ContentType from django.http.response import HttpResponseRedirect from django.urls import reverse -from .utility.email import send_templated_email, EmailSendingError from . import models logger = logging.getLogger(__name__) @@ -63,30 +62,16 @@ class DomainApplicationAdmin(AuditedAdmin): # Get the original application from the database original_obj = models.DomainApplication.objects.get(pk=obj.pk) - if obj.status != original_obj.status and obj.status == "investigating": - if ( - original_obj.submitter is None - or original_obj.submitter.email is None - ): - logger.warning( - "Cannot send status change (in review) email," - "no submitter email address." - ) - return - try: - print( - f"original_obj.submitter.email {original_obj.submitter.email}" - ) - send_templated_email( - "emails/status_change_in_review.txt", - "emails/status_change_in_review_subject.txt", - original_obj.submitter.email, - context={"application": obj}, - ) - except EmailSendingError: - logger.warning( - "Failed to send status change (in review) email", exc_info=True - ) + if ( + obj.status != original_obj.status + and obj.status == models.DomainApplication.INVESTIGATING + ): + # This is a transition annotated method in model which will throw an + # error if the condition is violated. To make this work, we need to + # call it on the original object which has the right status value, + # but pass the current object which contains the up-to-date data + # for the email. + original_obj.in_review(obj) super().save_model(request, obj, form, change) diff --git a/src/registrar/models/domain_application.py b/src/registrar/models/domain_application.py index fa3a2973c..3dae3d4a0 100644 --- a/src/registrar/models/domain_application.py +++ b/src/registrar/models/domain_application.py @@ -484,6 +484,9 @@ class DomainApplication(TimeStampedModel): ) return try: + logger.info( + f"Submission confirmation email sent to: {self.submitter.email}" + ) send_templated_email( "emails/submission_confirmation.txt", "emails/submission_confirmation_subject.txt", @@ -493,6 +496,32 @@ class DomainApplication(TimeStampedModel): except EmailSendingError: logger.warning("Failed to send confirmation email", exc_info=True) + def _send_in_review_email(self): + """Send an email that this application is now in review. + + The email goes to the email address that the submitter gave as their + contact information. If there is not submitter information, then do + nothing. + """ + if self.submitter is None or self.submitter.email is None: + logger.warning( + "Cannot send status change (in review) email," + "no submitter email address." + ) + return + try: + logging.info(f"In review email sent to: {self.submitter.email}") + send_templated_email( + "emails/status_change_in_review.txt", + "emails/status_change_in_review_subject.txt", + self.submitter.email, + context={"application": self}, + ) + except EmailSendingError: + logger.warning( + "Failed to send status change (in review) email", exc_info=True + ) + @transition(field="status", source=[STARTED, WITHDRAWN], target=SUBMITTED) def submit(self): """Submit an application that is started.""" @@ -541,6 +570,14 @@ class DomainApplication(TimeStampedModel): user=self.creator, domain=created_domain, role=UserDomainRole.Roles.ADMIN ) + @transition(field="status", source=SUBMITTED, target=INVESTIGATING) + def in_review(self, updated_domain_application): + """Investigate an application that has been submitted.""" + + # When an application is moved to in review, we need to send a + # confirmation email. This is a side-effect of the state transition + updated_domain_application._send_in_review_email() + # ## Form policies ### # # These methods control what questions need to be answered by applicants diff --git a/src/registrar/templates/emails/includes/application_summary.txt b/src/registrar/templates/emails/includes/application_summary.txt new file mode 100644 index 000000000..07519a8f0 --- /dev/null +++ b/src/registrar/templates/emails/includes/application_summary.txt @@ -0,0 +1,39 @@ +SUMMARY OF YOUR DOMAIN REQUEST + +Type of organization: +{{ application.get_organization_type_display }} + +Organization name and mailing address: +{% spaceless %}{{ application.organization_name }} +{{ application.address_line1 }}{% if application.address_line2 %} +{{ application.address_line2 }}{% endif %} +{{ application.city }}, {{ application.state_territory }} +{{ application.zipcode }}{% if application.urbanization %} +{{ application.urbanization }}{% endif %}{% endspaceless %} +{% if application.type_of_work %}{# if block makes one newline if it's false #} +Type of work: +{% spaceless %}{{ application.type_of_work }}{% endspaceless %} +{% endif %} +Authorizing official: +{% spaceless %}{% include "emails/includes/contact.txt" with contact=application.authorizing_official %}{% endspaceless %} +{% if application.current_websites.exists %}{# if block makes a newline #} +Current website for your organization: {% for site in application.current_websites.all %} +{% spaceless %}{{ site.website }}{% endspaceless %} +{% endfor %}{% endif %} +.gov domain: +{{ application.requested_domain.name }} +{% for site in application.alternative_domains.all %}{% spaceless %}{{ site.website }}{% endspaceless %} +{% endfor %} +Purpose of your domain: +{{ application.purpose }} + +Your contact information: +{% spaceless %}{% include "emails/includes/contact.txt" with contact=application.submitter %}{% endspaceless %} +{% if application.other_contacts.all %} +Other employees from your organization: +{% for other in application.other_contacts.all %} +{% spaceless %}{% include "emails/includes/contact.txt" with contact=other %}{% endspaceless %} +{% endfor %}{% endif %}{% if application.anything_else %} +Anything else we should know? +{{ application.anything_else }} +{% endif %} \ No newline at end of file diff --git a/src/registrar/templates/emails/status_change_in_review.txt b/src/registrar/templates/emails/status_change_in_review.txt index 9b7928502..63df669eb 100644 --- a/src/registrar/templates/emails/status_change_in_review.txt +++ b/src/registrar/templates/emails/status_change_in_review.txt @@ -34,45 +34,7 @@ requesting a .gov domain. ---------------------------------------------------------------- -SUMMARY OF YOUR DOMAIN REQUEST - -Type of organization: -{{ application.get_organization_type_display }} - -Organization name and mailing address: -{% spaceless %}{{ application.organization_name }} -{{ application.address_line1 }}{% if application.address_line2 %} -{{ application.address_line2 }}{% endif %} -{{ application.city }}, {{ application.state_territory }} -{{ application.zipcode }}{% if application.urbanization %} -{{ application.urbanization }}{% endif %}{% endspaceless %} -{% if application.type_of_work %}{# if block makes one newline if it's false #} -Type of work: -{% spaceless %}{{ application.type_of_work }}{% endspaceless %} -{% endif %} -Authorizing official: -{% spaceless %}{% include "emails/includes/contact.txt" with contact=application.authorizing_official %}{% endspaceless %} -{% if application.current_websites.exists %}{# if block makes a newline #} -Current website for your organization: {% for site in application.current_websites.all %} -{% spaceless %}{{ site.website }}{% endspaceless %} -{% endfor %}{% endif %} -.gov domain: -{{ application.requested_domain.name }} -{% for site in application.alternative_domains.all %}{% spaceless %}{{ site.website }}{% endspaceless %} -{% endfor %} -Purpose of your domain: -{{ application.purpose }} - -Your contact information: -{% spaceless %}{% include "emails/includes/contact.txt" with contact=application.submitter %}{% endspaceless %} -{% if application.other_contacts.all %} -Other employees from your organization: -{% for other in application.other_contacts.all %} -{% spaceless %}{% include "emails/includes/contact.txt" with contact=other %}{% endspaceless %} -{% endfor %}{% endif %}{% if application.anything_else %} -Anything else we should know? -{{ application.anything_else }} -{% endif %} +{% include 'emails/includes/application_summary.txt' %} ---------------------------------------------------------------- The .gov team diff --git a/src/registrar/templates/emails/submission_confirmation.txt b/src/registrar/templates/emails/submission_confirmation.txt index 6da239065..41fab8005 100644 --- a/src/registrar/templates/emails/submission_confirmation.txt +++ b/src/registrar/templates/emails/submission_confirmation.txt @@ -33,45 +33,7 @@ requesting a .gov domain. ---------------------------------------------------------------- -SUMMARY OF YOUR DOMAIN REQUEST - -Type of organization: -{{ application.get_organization_type_display }} - -Organization name and mailing address: -{% spaceless %}{{ application.organization_name }} -{{ application.address_line1 }}{% if application.address_line2 %} -{{ application.address_line2 }}{% endif %} -{{ application.city }}, {{ application.state_territory }} -{{ application.zipcode }}{% if application.urbanization %} -{{ application.urbanization }}{% endif %}{% endspaceless %} -{% if application.type_of_work %}{# if block makes one newline if it's false #} -Type of work: -{% spaceless %}{{ application.type_of_work }}{% endspaceless %} -{% endif %} -Authorizing official: -{% spaceless %}{% include "emails/includes/contact.txt" with contact=application.authorizing_official %}{% endspaceless %} -{% if application.current_websites.exists %}{# if block makes a newline #} -Current website for your organization: {% for site in application.current_websites.all %} -{% spaceless %}{{ site.website }}{% endspaceless %} -{% endfor %}{% endif %} -.gov domain: -{{ application.requested_domain.name }} -{% for site in application.alternative_domains.all %}{% spaceless %}{{ site.website }}{% endspaceless %} -{% endfor %} -Purpose of your domain: -{{ application.purpose }} - -Your contact information: -{% spaceless %}{% include "emails/includes/contact.txt" with contact=application.submitter %}{% endspaceless %} -{% if application.other_contacts.all %} -Other employees from your organization: -{% for other in application.other_contacts.all %} -{% spaceless %}{% include "emails/includes/contact.txt" with contact=other %}{% endspaceless %} -{% endfor %}{% endif %}{% if application.anything_else %} -Anything else we should know? -{{ application.anything_else }} -{% endif %} +{% include 'emails/includes/application_summary.txt' %} ---------------------------------------------------------------- The .gov team diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index 332b04c0e..dfc0787af 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -8,6 +8,8 @@ from typing import List, Dict from django.conf import settings from django.contrib.auth import get_user_model, login +from registrar.models import Contact, DraftDomain, Website, DomainApplication + def get_handlers(): """Obtain pointers to all StreamHandlers.""" @@ -84,3 +86,74 @@ class MockSESClient(Mock): def send_email(self, *args, **kwargs): self.EMAILS_SENT.append({"args": args, "kwargs": kwargs}) + + +def completed_application( + has_other_contacts=True, + has_current_website=True, + has_alternative_gov_domain=True, + has_type_of_work=True, + has_anything_else=True, + status=DomainApplication.STARTED, + user=False, +): + """A completed domain application.""" + if not user: + user = get_user_model().objects.create(username="username") + ao, _ = Contact.objects.get_or_create( + first_name="Testy", + last_name="Tester", + title="Chief Tester", + email="testy@town.com", + phone="(555) 555 5555", + ) + domain, _ = DraftDomain.objects.get_or_create(name="city.gov") + alt, _ = Website.objects.get_or_create(website="city1.gov") + current, _ = Website.objects.get_or_create(website="city.com") + you, _ = Contact.objects.get_or_create( + first_name="Testy you", + last_name="Tester you", + title="Admin Tester", + email="mayor@igorville.gov", + phone="(555) 555 5556", + ) + other, _ = Contact.objects.get_or_create( + first_name="Testy2", + last_name="Tester2", + title="Another Tester", + email="testy2@town.com", + phone="(555) 555 5557", + ) + domain_application_kwargs = dict( + organization_type="federal", + federal_type="executive", + purpose="Purpose of the site", + is_policy_acknowledged=True, + organization_name="Testorg", + address_line1="address 1", + address_line2="address 2", + state_territory="NY", + zipcode="10002", + authorizing_official=ao, + requested_domain=domain, + submitter=you, + creator=user, + status=status, + ) + if has_type_of_work: + domain_application_kwargs["type_of_work"] = "e-Government" + if has_anything_else: + domain_application_kwargs["anything_else"] = "There is more" + + application, _ = DomainApplication.objects.get_or_create( + **domain_application_kwargs + ) + + if has_other_contacts: + application.other_contacts.add(other) + if has_current_website: + application.current_websites.add(current) + if has_alternative_gov_domain: + application.alternative_domains.add(alt) + + return application diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 44739fd73..03a536e12 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -1,8 +1,8 @@ from django.test import TestCase, RequestFactory from django.contrib.admin.sites import AdminSite from registrar.admin import DomainApplicationAdmin -from django.contrib.auth import get_user_model -from registrar.models import Contact, DraftDomain, Website, DomainApplication, User +from registrar.models import DomainApplication, User +from .common import completed_application from django.conf import settings from unittest.mock import MagicMock @@ -14,73 +14,6 @@ class TestDomainApplicationAdmin(TestCase): self.site = AdminSite() self.factory = RequestFactory() - def _completed_application( - self, - has_other_contacts=True, - has_current_website=True, - has_alternative_gov_domain=True, - has_type_of_work=True, - has_anything_else=True, - ): - """A completed domain application.""" - user = get_user_model().objects.create(username="username") - ao, _ = Contact.objects.get_or_create( - first_name="Testy", - last_name="Tester", - title="Chief Tester", - email="testy@town.com", - phone="(555) 555 5555", - ) - domain, _ = DraftDomain.objects.get_or_create(name="city.gov") - alt, _ = Website.objects.get_or_create(website="city1.gov") - current, _ = Website.objects.get_or_create(website="city.com") - you, _ = Contact.objects.get_or_create( - first_name="Testy you", - last_name="Tester you", - title="Admin Tester", - email="mayor@igorville.gov", - phone="(555) 555 5556", - ) - other, _ = Contact.objects.get_or_create( - first_name="Testy2", - last_name="Tester2", - title="Another Tester", - email="testy2@town.com", - phone="(555) 555 5557", - ) - domain_application_kwargs = dict( - organization_type="federal", - federal_type="executive", - purpose="Purpose of the site", - is_policy_acknowledged=True, - organization_name="Testorg", - address_line1="address 1", - address_line2="address 2", - state_territory="NY", - zipcode="10002", - authorizing_official=ao, - requested_domain=domain, - submitter=you, - creator=user, - ) - if has_type_of_work: - domain_application_kwargs["type_of_work"] = "e-Government" - if has_anything_else: - domain_application_kwargs["anything_else"] = "There is more" - - application, _ = DomainApplication.objects.get_or_create( - **domain_application_kwargs - ) - - if has_other_contacts: - application.other_contacts.add(other) - if has_current_website: - application.current_websites.add(current) - if has_alternative_gov_domain: - application.alternative_domains.add(alt) - - return application - @boto3_mocking.patching def test_save_model_sends_email_on_property_change(self): # make sure there is no user with this email @@ -92,7 +25,7 @@ class TestDomainApplicationAdmin(TestCase): with boto3_mocking.clients.handler_for("sesv2", mock_client): # Create a sample application - application = self._completed_application() + application = completed_application(status=DomainApplication.SUBMITTED) # Create a mock request request = self.factory.post( diff --git a/src/registrar/tests/test_emails.py b/src/registrar/tests/test_emails.py index 4d7b22d01..b5c6cd428 100644 --- a/src/registrar/tests/test_emails.py +++ b/src/registrar/tests/test_emails.py @@ -2,82 +2,14 @@ from unittest.mock import MagicMock -from django.contrib.auth import get_user_model from django.test import TestCase +from .common import completed_application -from registrar.models import Contact, DraftDomain, Website, DomainApplication import boto3_mocking # type: ignore class TestEmails(TestCase): - def _completed_application( - self, - has_other_contacts=True, - has_current_website=True, - has_alternative_gov_domain=True, - has_type_of_work=True, - has_anything_else=True, - ): - """A completed domain application.""" - user = get_user_model().objects.create(username="username") - ao, _ = Contact.objects.get_or_create( - first_name="Testy", - last_name="Tester", - title="Chief Tester", - email="testy@town.com", - phone="(555) 555 5555", - ) - domain, _ = DraftDomain.objects.get_or_create(name="city.gov") - alt, _ = Website.objects.get_or_create(website="city1.gov") - current, _ = Website.objects.get_or_create(website="city.com") - you, _ = Contact.objects.get_or_create( - first_name="Testy you", - last_name="Tester you", - title="Admin Tester", - email="testy-admin@town.com", - phone="(555) 555 5556", - ) - other, _ = Contact.objects.get_or_create( - first_name="Testy2", - last_name="Tester2", - title="Another Tester", - email="testy2@town.com", - phone="(555) 555 5557", - ) - domain_application_kwargs = dict( - organization_type="federal", - federal_type="executive", - purpose="Purpose of the site", - is_policy_acknowledged=True, - organization_name="Testorg", - address_line1="address 1", - address_line2="address 2", - state_territory="NY", - zipcode="10002", - authorizing_official=ao, - requested_domain=domain, - submitter=you, - creator=user, - ) - if has_type_of_work: - domain_application_kwargs["type_of_work"] = "e-Government" - if has_anything_else: - domain_application_kwargs["anything_else"] = "There is more" - - application, _ = DomainApplication.objects.get_or_create( - **domain_application_kwargs - ) - - if has_other_contacts: - application.other_contacts.add(other) - if has_current_website: - application.current_websites.add(current) - if has_alternative_gov_domain: - application.alternative_domains.add(alt) - - return application - def setUp(self): self.mock_client_class = MagicMock() self.mock_client = self.mock_client_class.return_value @@ -85,7 +17,7 @@ class TestEmails(TestCase): @boto3_mocking.patching def test_submission_confirmation(self): """Submission confirmation email works.""" - application = self._completed_application() + application = completed_application() with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): application.submit() @@ -122,7 +54,7 @@ class TestEmails(TestCase): @boto3_mocking.patching def test_submission_confirmation_no_current_website_spacing(self): """Test line spacing without current_website.""" - application = self._completed_application(has_current_website=False) + application = completed_application(has_current_website=False) with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): application.submit() _, kwargs = self.mock_client.send_email.call_args @@ -134,7 +66,7 @@ class TestEmails(TestCase): @boto3_mocking.patching def test_submission_confirmation_current_website_spacing(self): """Test line spacing with current_website.""" - application = self._completed_application(has_current_website=True) + application = completed_application(has_current_website=True) with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): application.submit() _, kwargs = self.mock_client.send_email.call_args @@ -147,7 +79,7 @@ class TestEmails(TestCase): @boto3_mocking.patching def test_submission_confirmation_other_contacts_spacing(self): """Test line spacing with other contacts.""" - application = self._completed_application(has_other_contacts=True) + application = completed_application(has_other_contacts=True) with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): application.submit() _, kwargs = self.mock_client.send_email.call_args @@ -160,7 +92,7 @@ class TestEmails(TestCase): @boto3_mocking.patching def test_submission_confirmation_no_other_contacts_spacing(self): """Test line spacing without other contacts.""" - application = self._completed_application(has_other_contacts=False) + application = completed_application(has_other_contacts=False) with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): application.submit() _, kwargs = self.mock_client.send_email.call_args @@ -172,7 +104,7 @@ class TestEmails(TestCase): @boto3_mocking.patching def test_submission_confirmation_alternative_govdomain_spacing(self): """Test line spacing with alternative .gov domain.""" - application = self._completed_application(has_alternative_gov_domain=True) + application = completed_application(has_alternative_gov_domain=True) with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): application.submit() _, kwargs = self.mock_client.send_email.call_args @@ -184,7 +116,7 @@ class TestEmails(TestCase): @boto3_mocking.patching def test_submission_confirmation_no_alternative_govdomain_spacing(self): """Test line spacing without alternative .gov domain.""" - application = self._completed_application(has_alternative_gov_domain=False) + application = completed_application(has_alternative_gov_domain=False) with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): application.submit() _, kwargs = self.mock_client.send_email.call_args @@ -196,7 +128,7 @@ class TestEmails(TestCase): @boto3_mocking.patching def test_submission_confirmation_type_of_work_spacing(self): """Test line spacing with type of work.""" - application = self._completed_application(has_type_of_work=True) + application = completed_application(has_type_of_work=True) with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): application.submit() _, kwargs = self.mock_client.send_email.call_args @@ -208,7 +140,7 @@ class TestEmails(TestCase): @boto3_mocking.patching def test_submission_confirmation_no_type_of_work_spacing(self): """Test line spacing without type of work.""" - application = self._completed_application(has_type_of_work=False) + application = completed_application(has_type_of_work=False) with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): application.submit() _, kwargs = self.mock_client.send_email.call_args @@ -220,7 +152,7 @@ class TestEmails(TestCase): @boto3_mocking.patching def test_submission_confirmation_anything_else_spacing(self): """Test line spacing with anything else.""" - application = self._completed_application(has_anything_else=True) + application = completed_application(has_anything_else=True) with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): application.submit() _, kwargs = self.mock_client.send_email.call_args @@ -231,7 +163,7 @@ class TestEmails(TestCase): @boto3_mocking.patching def test_submission_confirmation_no_anything_else_spacing(self): """Test line spacing without anything else.""" - application = self._completed_application(has_anything_else=False) + application = completed_application(has_anything_else=False) with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): application.submit() _, kwargs = self.mock_client.send_email.call_args diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index bf1cef2f2..410281f1c 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -5,6 +5,7 @@ from django.conf import settings from django.test import Client, TestCase from django.urls import reverse from django.contrib.auth import get_user_model +from .common import completed_application from django_webtest import WebTest # type: ignore import boto3_mocking # type: ignore @@ -1370,85 +1371,18 @@ class TestApplicationStatus(TestWithUser, WebTest): self.app.set_user(self.user.username) self.client.force_login(self.user) - def _completed_application( - self, - has_other_contacts=True, - has_current_website=True, - has_alternative_gov_domain=True, - has_type_of_work=True, - has_anything_else=True, - ): - """A completed domain application.""" - ao, _ = Contact.objects.get_or_create( - first_name="Testy", - last_name="Tester", - title="Chief Tester", - email="testy@town.com", - phone="(555) 555 5555", - ) - domain, _ = DraftDomain.objects.get_or_create(name="citystatus.gov") - alt, _ = Website.objects.get_or_create(website="city1.gov") - current, _ = Website.objects.get_or_create(website="city.com") - you, _ = Contact.objects.get_or_create( - first_name="Testy you", - last_name="Tester you", - title="Admin Tester", - email="testy-admin@town.com", - phone="(555) 555 5556", - ) - other, _ = Contact.objects.get_or_create( - first_name="Testy2", - last_name="Tester2", - title="Another Tester", - email="testy2@town.com", - phone="(555) 555 5557", - ) - domain_application_kwargs = dict( - organization_type="federal", - federal_type="executive", - purpose="Purpose of the site", - is_policy_acknowledged=True, - organization_name="Testorg", - address_line1="address 1", - address_line2="address 2", - state_territory="NY", - zipcode="10002", - authorizing_official=ao, - requested_domain=domain, - submitter=you, - creator=self.user, - ) - if has_type_of_work: - domain_application_kwargs["type_of_work"] = "e-Government" - if has_anything_else: - domain_application_kwargs["anything_else"] = "There is more" - - application, _ = DomainApplication.objects.get_or_create( - **domain_application_kwargs - ) - - application.status = DomainApplication.SUBMITTED - application.save() - - if has_other_contacts: - application.other_contacts.add(other) - if has_current_website: - application.current_websites.add(current) - if has_alternative_gov_domain: - application.alternative_domains.add(alt) - - return application - def test_application_status(self): """Checking application status page""" - application = self._completed_application() + application = completed_application( + status=DomainApplication.SUBMITTED, user=self.user + ) application.save() home_page = self.app.get("/") - self.assertContains(home_page, "citystatus.gov") + self.assertContains(home_page, "city.gov") # click the "Manage" link detail_page = home_page.click("Manage") - self.assertContains(detail_page, "citystatus.gov") + self.assertContains(detail_page, "city.gov") self.assertContains(detail_page, "Chief Tester") self.assertContains(detail_page, "testy@town.com") self.assertContains(detail_page, "Admin Tester") @@ -1456,14 +1390,16 @@ class TestApplicationStatus(TestWithUser, WebTest): def test_application_withdraw(self): """Checking application status page""" - application = self._completed_application() + application = completed_application( + status=DomainApplication.SUBMITTED, user=self.user + ) application.save() home_page = self.app.get("/") - self.assertContains(home_page, "citystatus.gov") + self.assertContains(home_page, "city.gov") # click the "Manage" link detail_page = home_page.click("Manage") - self.assertContains(detail_page, "citystatus.gov") + self.assertContains(detail_page, "city.gov") self.assertContains(detail_page, "Chief Tester") self.assertContains(detail_page, "testy@town.com") self.assertContains(detail_page, "Admin Tester") @@ -1485,7 +1421,9 @@ class TestApplicationStatus(TestWithUser, WebTest): def test_application_status_no_permissions(self): """Can't access applications without being the creator.""" - application = self._completed_application() + application = completed_application( + status=DomainApplication.SUBMITTED, user=self.user + ) other_user = User() other_user.save() application.creator = other_user From 76afc9780ee5d06d07b552507a9d188c8f5555e5 Mon Sep 17 00:00:00 2001 From: rachidatecs Date: Mon, 12 Jun 2023 17:36:52 -0400 Subject: [PATCH 07/26] linting --- src/registrar/models/domain_application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/models/domain_application.py b/src/registrar/models/domain_application.py index 8a32333bf..619557d20 100644 --- a/src/registrar/models/domain_application.py +++ b/src/registrar/models/domain_application.py @@ -577,7 +577,7 @@ class DomainApplication(TimeStampedModel): # When an application is moved to in review, we need to send a # confirmation email. This is a side-effect of the state transition updated_domain_application._send_in_review_email() - + @transition(field="status", source=[SUBMITTED, INVESTIGATING], target=WITHDRAWN) def withdraw(self): """Withdraw an application that has been submitted.""" From 95932b87169573bc642171c4e30f18a3f8b32a46 Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Mon, 12 Jun 2023 15:14:12 -0700 Subject: [PATCH 08/26] Add new developer sandbox 'gd' infrastructure --- .github/workflows/migrate.yaml | 1 + .github/workflows/reset-db.yaml | 1 + ops/manifests/manifest-gd.yaml | 29 +++++++++++++++++++++++++++++ src/registrar/config/settings.py | 1 + 4 files changed, 32 insertions(+) create mode 100644 ops/manifests/manifest-gd.yaml diff --git a/.github/workflows/migrate.yaml b/.github/workflows/migrate.yaml index 800bfb809..d13711b8b 100644 --- a/.github/workflows/migrate.yaml +++ b/.github/workflows/migrate.yaml @@ -14,6 +14,7 @@ on: description: Which environment should we run migrations for? options: - stable + - gd - ko - ab - bl diff --git a/.github/workflows/reset-db.yaml b/.github/workflows/reset-db.yaml index 57602659c..dff8e83bf 100644 --- a/.github/workflows/reset-db.yaml +++ b/.github/workflows/reset-db.yaml @@ -15,6 +15,7 @@ on: description: Which environment should we flush and re-load data for? options: - stable + - gd - ko - ab - bl diff --git a/ops/manifests/manifest-gd.yaml b/ops/manifests/manifest-gd.yaml new file mode 100644 index 000000000..f11758858 --- /dev/null +++ b/ops/manifests/manifest-gd.yaml @@ -0,0 +1,29 @@ +--- +applications: +- name: getgov-gd + buildpacks: + - python_buildpack + path: ../../src + instances: 1 + memory: 512M + stack: cflinuxfs4 + timeout: 180 + command: ./run.sh + health-check-type: http + health-check-http-endpoint: /health + env: + # Send stdout and stderr straight to the terminal without buffering + PYTHONUNBUFFERED: yup + # Tell Django where to find its configuration + DJANGO_SETTINGS_MODULE: registrar.config.settings + # Tell Django where it is being hosted + DJANGO_BASE_URL: https://getgov-gd.app.cloud.gov + # Tell Django how much stuff to log + DJANGO_LOG_LEVEL: INFO + # default public site location + GETGOV_PUBLIC_SITE_URL: https://beta.get.gov + routes: + - route: getgov-gd.app.cloud.gov + services: + - getgov-credentials + - getgov-gd-database diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index 0f032904f..73a8fc64e 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -564,6 +564,7 @@ SECURE_SSL_REDIRECT = True # web server configurations. ALLOWED_HOSTS = [ "getgov-stable.app.cloud.gov", + "getgov-gd.app.cloud.gov", "getgov-ko.app.cloud.gov", "getgov-ab.app.cloud.gov", "getgov-bl.app.cloud.gov", From c4ac3099c35f0f892e57e347fbd880aa234223f4 Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Mon, 12 Jun 2023 15:31:25 -0700 Subject: [PATCH 09/26] updated deploy variables with rb --- .github/workflows/deploy-sandbox.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-sandbox.yaml b/.github/workflows/deploy-sandbox.yaml index 7c2a0fe6a..29ef02f24 100644 --- a/.github/workflows/deploy-sandbox.yaml +++ b/.github/workflows/deploy-sandbox.yaml @@ -17,6 +17,7 @@ jobs: || startsWith(github.head_ref, 'ab/') || startsWith(github.head_ref, 'bl/') || startsWith(github.head_ref, 'rjm/') + || startsWith(github.head_ref, 'rb/') outputs: environment: ${{ steps.var.outputs.environment}} runs-on: "ubuntu-latest" From 0f38c3d7a03a17ea5f791cab29b4503d8b57b765 Mon Sep 17 00:00:00 2001 From: Seamus Johnston Date: Tue, 13 Jun 2023 08:25:12 -0500 Subject: [PATCH 10/26] Remove whoami page --- src/.pa11yci | 3 --- src/registrar/config/urls.py | 1 - src/registrar/templates/base.html | 2 +- src/registrar/templates/whoami.html | 10 ---------- src/registrar/tests/test_views.py | 13 ------------- src/registrar/views/__init__.py | 1 - src/registrar/views/whoami.py | 8 -------- 7 files changed, 1 insertion(+), 37 deletions(-) delete mode 100644 src/registrar/templates/whoami.html delete mode 100644 src/registrar/views/whoami.py diff --git a/src/.pa11yci b/src/.pa11yci index e641d58e4..4efa264f9 100644 --- a/src/.pa11yci +++ b/src/.pa11yci @@ -2,13 +2,10 @@ "defaults": { "concurrency": 1, "timeout": 30000, - "hideElements": "a[href='/whoami/']" - }, "urls": [ "http://localhost:8080/", "http://localhost:8080/health/", - "http://localhost:8080/whoami/", "http://localhost:8080/register/", "http://localhost:8080/register/organization/", "http://localhost:8080/register/org_federal/", diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py index 0ef9fbe36..c21d0206c 100644 --- a/src/registrar/config/urls.py +++ b/src/registrar/config/urls.py @@ -45,7 +45,6 @@ for step, view in [ urlpatterns = [ path("", views.index, name="home"), - path("whoami/", views.whoami, name="whoami"), path("admin/", admin.site.urls), path( "application//edit/", diff --git a/src/registrar/templates/base.html b/src/registrar/templates/base.html index 3ca280a0b..977366e47 100644 --- a/src/registrar/templates/base.html +++ b/src/registrar/templates/base.html @@ -152,7 +152,7 @@
  • {% if user.is_authenticated %} - {{ user.email }} + {{ user.email }}
  • | diff --git a/src/registrar/templates/whoami.html b/src/registrar/templates/whoami.html deleted file mode 100644 index 69c4d9672..000000000 --- a/src/registrar/templates/whoami.html +++ /dev/null @@ -1,10 +0,0 @@ -{% extends 'base.html' %} - -{% block title %} Hello {% endblock %} -{% block content %} -
    -

    Hello {{ user.last_name|default:"No last name given" }}, {{ user.first_name|default:"No first name given" }} <{{ user.email }}>!

    - -

    Click here to log out

    -
    -{% endblock %} diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index ffe4cb671..19324258b 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -39,12 +39,6 @@ class TestViews(TestCase): response = self.client.get("/") self.assertEqual(response.status_code, 302) - def test_whoami_page_no_user(self): - """Whoami page not accessible without a logged-in user.""" - response = self.client.get("/whoami/") - self.assertEqual(response.status_code, 302) - self.assertIn("?next=/whoami/", response.headers["Location"]) - def test_application_form_not_logged_in(self): """Application form not accessible without a logged-in user.""" response = self.client.get("/register/") @@ -99,13 +93,6 @@ class LoggedInTests(TestWithUser): # clean up role.delete() - def test_whoami_page(self): - """User information appears on the whoami page.""" - response = self.client.get("/whoami/") - self.assertContains(response, self.user.first_name) - self.assertContains(response, self.user.last_name) - self.assertContains(response, self.user.email) - def test_application_form_view(self): response = self.client.get("/register/", follow=True) self.assertContains( diff --git a/src/registrar/views/__init__.py b/src/registrar/views/__init__.py index ed1d8ba39..f37d2724a 100644 --- a/src/registrar/views/__init__.py +++ b/src/registrar/views/__init__.py @@ -12,4 +12,3 @@ from .domain import ( ) from .health import * from .index import * -from .whoami import * diff --git a/src/registrar/views/whoami.py b/src/registrar/views/whoami.py deleted file mode 100644 index 20bde778b..000000000 --- a/src/registrar/views/whoami.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.shortcuts import render -from django.contrib.auth.decorators import login_required - - -@login_required -def whoami(request): - """This is the first page someone goes to after logging in.""" - return render(request, "whoami.html") From 737184130f6589f20aa816f7af1ef118803fd035 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Tue, 13 Jun 2023 14:10:54 -0500 Subject: [PATCH 11/26] Add more ops debugging tips --- docs/operations/README.md | 112 +++++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/docs/operations/README.md b/docs/operations/README.md index 0f79e3420..3a44f4662 100644 --- a/docs/operations/README.md +++ b/docs/operations/README.md @@ -1,5 +1,4 @@ # Operations -======================== Some basic information and setup steps are included in this README. @@ -46,3 +45,114 @@ Your sandbox space should've been setup as part of the onboarding process. If th We are using [WhiteNoise](http://whitenoise.evans.io/en/stable/index.html) plugin to serve our static assets on cloud.gov. This plugin is added to the `MIDDLEWARE` list in our apps `settings.py`. Note that it’s a good idea to run `collectstatic` locally or in the docker container before pushing files up to your sandbox. This is because `collectstatic` relies on timestamps when deciding to whether to overwrite the existing assets in `/public`. Due the way files are uploaded, the compiled css in the `/assets/css` folder on your sandbox will have a slightly earlier timestamp than the files in `/public/css`, and consequently running `collectstatic` on your sandbox will not update `public/css` as you may expect. For convenience, both the `deploy.sh` and `build.sh` scripts will take care of that. + +# Debugging + +Debugging errors observed in applications running on Cloud.gov requires being +able to see the log information from the environment that the application is +running in. There are (at least) three different ways to see that information: +Cloud.gov dashboard, CloudFoundry CLI application, and Cloud.gov Kibana logging +queries. There is also SSH access into Cloud.gov containers and Github Actions +that can be used for specific tasks. + +## Cloud.gov dashboard + +At there is a list for all of the +applications that a Cloud.gov user has access to. Clicking on an application +goes to a screen for that individual application, e.g. +. +On that page is a left-hand link for "Log Stream" e.g. +. +That log stream shows a stream of Cloud.gov log messages. Cloud.gov has +different layers that log requests. One is `RTR` which is the router within +Cloud.gov. Messages from our Django app are prefixed with `APP/PROC/WEB`. While +it is possible to search inside the browser for particular log messages, this +is not a sophisticated interface for querying logs. + +## CloudFoundry CLI + +When logged in with the CloudFoundry CLI (see +[above](#authenticating-to-cloudgov-via-the-command-line)) Cloudfoundry +application logs can be viewed with the `cf logs ` where +`` is the name of the application in the currently targeted space. +By default `cf logs` starts a streaming view of log messages from the +application. It appears to show the same information as the dashboard web +application, but in the terminal. There is a `--recent` option that will dump +things that happened prior to the current time rather than starting a stream of +the present log messages, but that is also not a full log archive and search +system. + +CloudFoundry also offers a `run-task` command that can be used to run a single +command in the running Cloud.gov container. For example, to run our Django +admin command that loads test fixture data: + +``` +cf run-task getgov-nmb --command "./manage.py load" --name fixtures +``` + +However, this task runs asynchronously in the background without any command +output, so it can sometimes be hard to know if the command has completed and if +so, if it was successful. + +## Cloud.gov Kibana + +Cloud.gov provides an instance of the log query program Kibana at +. Kibana is powerful, but also complicated software +that can take time to learn how to use most effectively. A few hints: + + - Set the timeframe of the display appropriately, the default is the last + 15 minutes which may not show any results in some environments. + + - Kibana queries and filters can be used to narrow in on particular + environments. Try the query `@source.type:APP` to focus on messages from the + Django application or `@cf.app:"getgov-nmb"` to see results from a single + environment. + +Currently, our application emits Python's default log format which is textual +and not record-based. In particular, tracebacks are on multiple lines and show +up in Kibana as multiple records that are not necessarily connected. As the +application gets closer to production, we may want to switch to a JSON log format +where errors will be captured by Kibana as a single message, however with a +slightly more difficult developer experience when reading logs by eyeball. + + +## SSH access + +The CloudFoundry CLI provides SSH access to the running container of an +application. Use `cf ssh ` to SSH into the container. To make sure +that your shell is seeing the same configuration as the running application, be +sure to run `/tmp/lifecycle/shell` very first. + +Inside the container, the python code should be in `/app` and you can check +there to see if the expected version of code is deployed in a particular file. +There is no hot-reloading inside the container, so it isn't possible to make +code changes there and see the results reflected in the running application. +(Templates may be read directly from disk every page load so it is possible +that you could change a page template and see the result in the application.) + +Inside the container, it can be useful to run various Django admin commands +using `./manage.py`. For example, `./manage.py shell` can be used to give a +python interpreter where code can be run to modify objects in the database, say +to make a user an administrator. + +## Github Actions + +In order to allow some ops activities by people without CloudFoundry on a +laptop, we have some ops-related actions under +. + +### Migrate data + +This Github action runs Django's `manage.py migrate` command on the specified +environment. **This is the first thing to try when fixing 500 errors from an +application environment**. The migrations should be idempotent, so running the +same migrations more than once should never cause an additional problem. + +### Reset database + +Very occasionally, there are migrations that don't succeed when run against a +database with data already in it. This action drops the database and re-creates +it with the latest model schema. Once launched, this should never be used on +the `stable` environment, but during development, it may be useful on the +various sandbox environments. After launch, some schema changes may take the +involvement of a skilled DBA to fix problems like this. From 485fd89e31c820437720f323a4da9c4d660b9172 Mon Sep 17 00:00:00 2001 From: Katherine-Osos <119689946+Katherine-Osos@users.noreply.github.com> Date: Tue, 13 Jun 2023 14:28:19 -0500 Subject: [PATCH 12/26] Add Bolding to Tribal AO Page (#713) --- src/registrar/templates/includes/ao_example.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/templates/includes/ao_example.html b/src/registrar/templates/includes/ao_example.html index 5b1028a69..8a9d22915 100644 --- a/src/registrar/templates/includes/ao_example.html +++ b/src/registrar/templates/includes/ao_example.html @@ -44,7 +44,7 @@

    Domain requests from state legislatures and courts must be authorized by an agency’s Chief Information Officer or highest-ranking executive.

    {% elif organization_type == 'tribal' %} -

    Domain requests from federally-recognized tribal governments must be authorized by the leader of the tribe, as recognized by the Bureau of Indian Affairs.

    -

    Domain requests from state-recognized tribal governments must be authorized by the leader of the tribe, as determined by the state’s tribal recognition initiative.

    +

    Domain requests from federally-recognized tribal governments must be authorized by the leader of the tribe, as recognized by the Bureau of Indian Affairs.

    +

    Domain requests from state-recognized tribal governments must be authorized by the leader of the tribe, as determined by the state’s tribal recognition initiative.

    {% endif %} From 9c43fb65a94eef6dc569be84d89d77f288f8af44 Mon Sep 17 00:00:00 2001 From: rachidatecs Date: Tue, 13 Jun 2023 15:43:53 -0400 Subject: [PATCH 13/26] Clean up domain in review assignment in unit test --- src/registrar/tests/test_admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 03a536e12..2f2e1190b 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -36,7 +36,7 @@ class TestDomainApplicationAdmin(TestCase): model_admin = DomainApplicationAdmin(DomainApplication, self.site) # Modify the application's property - application.status = "investigating" + application.status = DomainApplication.INVESTIGATING # Use the model admin's save_model method model_admin.save_model(request, application, form=None, change=True) From e3313fb6abfab164950519e0adfb8b9b2b2a6357 Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Tue, 13 Jun 2023 13:51:09 -0700 Subject: [PATCH 14/26] added deploy variable for gd --- .github/workflows/deploy-sandbox.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-sandbox.yaml b/.github/workflows/deploy-sandbox.yaml index 08f03dbe9..e787c0544 100644 --- a/.github/workflows/deploy-sandbox.yaml +++ b/.github/workflows/deploy-sandbox.yaml @@ -18,6 +18,7 @@ jobs: || startsWith(github.head_ref, 'bl/') || startsWith(github.head_ref, 'rjm/') || startsWith(github.head_ref, 'ko/') + || startsWith(github.head_ref, 'gd/') outputs: environment: ${{ steps.var.outputs.environment}} runs-on: "ubuntu-latest" From 1ac56afe20af62835e3858f4b2d7ed24c260aee5 Mon Sep 17 00:00:00 2001 From: rachidatecs Date: Tue, 13 Jun 2023 17:44:19 -0400 Subject: [PATCH 15/26] additional comments, move log statements after the actions they're logging. --- src/registrar/admin.py | 2 +- src/registrar/models/domain_application.py | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 957a51867..c8d48024b 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -68,7 +68,7 @@ class DomainApplicationAdmin(AuditedAdmin): ): # This is a transition annotated method in model which will throw an # error if the condition is violated. To make this work, we need to - # call it on the original object which has the right status value, + # call it on the original object which has the right status value, # but pass the current object which contains the up-to-date data # for the email. original_obj.in_review(obj) diff --git a/src/registrar/models/domain_application.py b/src/registrar/models/domain_application.py index 619557d20..5ed887a51 100644 --- a/src/registrar/models/domain_application.py +++ b/src/registrar/models/domain_application.py @@ -484,15 +484,15 @@ class DomainApplication(TimeStampedModel): ) return try: - logger.info( - f"Submission confirmation email sent to: {self.submitter.email}" - ) send_templated_email( "emails/submission_confirmation.txt", "emails/submission_confirmation_subject.txt", self.submitter.email, context={"application": self}, ) + logger.info( + f"Submission confirmation email sent to: {self.submitter.email}" + ) except EmailSendingError: logger.warning("Failed to send confirmation email", exc_info=True) @@ -510,13 +510,15 @@ class DomainApplication(TimeStampedModel): ) return try: - logging.info(f"In review email sent to: {self.submitter.email}") send_templated_email( "emails/status_change_in_review.txt", "emails/status_change_in_review_subject.txt", self.submitter.email, context={"application": self}, ) + logging.info( + f"In review email sent to: {self.submitter.email}" + ) except EmailSendingError: logger.warning( "Failed to send status change (in review) email", exc_info=True @@ -572,7 +574,12 @@ class DomainApplication(TimeStampedModel): @transition(field="status", source=SUBMITTED, target=INVESTIGATING) def in_review(self, updated_domain_application): - """Investigate an application that has been submitted.""" + """Investigate an application that has been submitted. + + This method is called in admin.py on the original application + which has the correct status value, but is passed the changed + application which has the up-to-date data that we'll use + in the email.""" # When an application is moved to in review, we need to send a # confirmation email. This is a side-effect of the state transition From c2a2755060a740032b2b73a4898a1efd2eeecc87 Mon Sep 17 00:00:00 2001 From: rachidatecs Date: Tue, 13 Jun 2023 17:54:33 -0400 Subject: [PATCH 16/26] linting --- src/registrar/models/domain_application.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/registrar/models/domain_application.py b/src/registrar/models/domain_application.py index 5ed887a51..57be80b36 100644 --- a/src/registrar/models/domain_application.py +++ b/src/registrar/models/domain_application.py @@ -516,9 +516,7 @@ class DomainApplication(TimeStampedModel): self.submitter.email, context={"application": self}, ) - logging.info( - f"In review email sent to: {self.submitter.email}" - ) + logging.info(f"In review email sent to: {self.submitter.email}") except EmailSendingError: logger.warning( "Failed to send status change (in review) email", exc_info=True @@ -575,7 +573,7 @@ class DomainApplication(TimeStampedModel): @transition(field="status", source=SUBMITTED, target=INVESTIGATING) def in_review(self, updated_domain_application): """Investigate an application that has been submitted. - + This method is called in admin.py on the original application which has the correct status value, but is passed the changed application which has the up-to-date data that we'll use From 46a06578a59c30d4ddab93628e1a36ed50cdafb5 Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Wed, 14 Jun 2023 19:51:34 -0700 Subject: [PATCH 17/26] added the infra for staging sandbox --- .github/workflows/migrate.yaml | 1 + .github/workflows/reset-db.yaml | 1 + ops/manifests/manifest-staging.yaml | 29 +++++++++++++++++++++++++++++ src/registrar/config/settings.py | 1 + 4 files changed, 32 insertions(+) create mode 100644 ops/manifests/manifest-staging.yaml diff --git a/.github/workflows/migrate.yaml b/.github/workflows/migrate.yaml index 800bfb809..b8f687be5 100644 --- a/.github/workflows/migrate.yaml +++ b/.github/workflows/migrate.yaml @@ -14,6 +14,7 @@ on: description: Which environment should we run migrations for? options: - stable + - staging - ko - ab - bl diff --git a/.github/workflows/reset-db.yaml b/.github/workflows/reset-db.yaml index 57602659c..85010abd1 100644 --- a/.github/workflows/reset-db.yaml +++ b/.github/workflows/reset-db.yaml @@ -15,6 +15,7 @@ on: description: Which environment should we flush and re-load data for? options: - stable + - staging - ko - ab - bl diff --git a/ops/manifests/manifest-staging.yaml b/ops/manifests/manifest-staging.yaml new file mode 100644 index 000000000..93c44071c --- /dev/null +++ b/ops/manifests/manifest-staging.yaml @@ -0,0 +1,29 @@ +--- +applications: +- name: getgov-staging + buildpacks: + - python_buildpack + path: ../../src + instances: 1 + memory: 512M + stack: cflinuxfs4 + timeout: 180 + command: ./run.sh + health-check-type: http + health-check-http-endpoint: /health + env: + # Send stdout and stderr straight to the terminal without buffering + PYTHONUNBUFFERED: yup + # Tell Django where to find its configuration + DJANGO_SETTINGS_MODULE: registrar.config.settings + # Tell Django where it is being hosted + DJANGO_BASE_URL: https://getgov-staging.app.cloud.gov + # Tell Django how much stuff to log + DJANGO_LOG_LEVEL: INFO + # default public site location + GETGOV_PUBLIC_SITE_URL: https://beta.get.gov + routes: + - route: getgov-staging.app.cloud.gov + services: + - getgov-credentials + - getgov-staging-database diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index 0f032904f..abfed1e68 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -564,6 +564,7 @@ SECURE_SSL_REDIRECT = True # web server configurations. ALLOWED_HOSTS = [ "getgov-stable.app.cloud.gov", + "getgov-staging.app.cloud.gov", "getgov-ko.app.cloud.gov", "getgov-ab.app.cloud.gov", "getgov-bl.app.cloud.gov", From 8e149cb59789f6b9d790f2b02688699e0e040caf Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Thu, 15 Jun 2023 10:01:30 -0700 Subject: [PATCH 18/26] add staging deploy workflow --- .github/workflows/deploy-staging.yaml | 41 +++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/deploy-staging.yaml diff --git a/.github/workflows/deploy-staging.yaml b/.github/workflows/deploy-staging.yaml new file mode 100644 index 000000000..068751c30 --- /dev/null +++ b/.github/workflows/deploy-staging.yaml @@ -0,0 +1,41 @@ +# This workflow runs on pushes of tagged commits. +# "Releases" of tagged commits will deploy selected branch to staging. + +name: Build and deploy staging for tagged release + +on: + push: + paths-ignore: + - 'docs/**' + - '**.md' + - '.gitignore' + + tags: + - staging-* + +jobs: + deploy-staging: + if: ${{ github.ref_type == 'tag' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Compile USWDS assets + working-directory: ./src + run: | + docker compose run node npm install && + docker compose run node npx gulp copyAssets && + docker compose run node npx gulp compile + - name: Collect static assets + working-directory: ./src + run: docker compose run app python manage.py collectstatic --no-input + - name: Deploy to cloud.gov sandbox + uses: 18f/cg-deploy-action@main + env: + DEPLOY_NOW: thanks + with: + cf_username: ${{ secrets.CF_STAGING_USERNAME }} + cf_password: ${{ secrets.CF_STAGING_PASSWORD }} + cf_org: cisa-getgov-prototyping + cf_space: staging + push_arguments: "-f ops/manifests/manifest-staging.yaml" From 32f491695115a01b3b54ece53ede0f6958a542ec Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Thu, 15 Jun 2023 10:02:25 -0700 Subject: [PATCH 19/26] fixed missing -i tag that causes base64 command to fail --- ops/scripts/create_dev_sandbox.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ops/scripts/create_dev_sandbox.sh b/ops/scripts/create_dev_sandbox.sh index df10d8d90..6ff34700e 100755 --- a/ops/scripts/create_dev_sandbox.sh +++ b/ops/scripts/create_dev_sandbox.sh @@ -65,7 +65,7 @@ done echo "Creating new cloud.gov credentials for $1..." django_key=$(python3 -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())') openssl req -nodes -x509 -days 365 -newkey rsa:2048 -keyout private-$1.pem -out public-$1.crt -login_key=$(base64 private-$1.pem) +login_key=$(base64 -i private-$1.pem) jq -n --arg django_key "$django_key" --arg login_key "$login_key" '{"DJANGO_SECRET_KEY":$django_key,"DJANGO_SECRET_LOGIN_KEY":$login_key}' > credentials-$1.json cf cups getgov-credentials -p credentials-$1.json From e6cc91183d8b7e035975bd6c02f7b7e6afbcd705 Mon Sep 17 00:00:00 2001 From: Neil MartinsenBurrell Date: Thu, 15 Jun 2023 15:27:46 -0500 Subject: [PATCH 20/26] Update docs/operations/README.md Co-authored-by: Brandon Lenz --- docs/operations/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/operations/README.md b/docs/operations/README.md index 3a44f4662..1adee0521 100644 --- a/docs/operations/README.md +++ b/docs/operations/README.md @@ -87,7 +87,7 @@ command in the running Cloud.gov container. For example, to run our Django admin command that loads test fixture data: ``` -cf run-task getgov-nmb --command "./manage.py load" --name fixtures +cf run-task getgov-{environment} --command "./manage.py load" --name fixtures ``` However, this task runs asynchronously in the background without any command From 0ddecf3e373a5c002fc4227f5e648206cc245b39 Mon Sep 17 00:00:00 2001 From: Neil MartinsenBurrell Date: Thu, 15 Jun 2023 15:27:53 -0500 Subject: [PATCH 21/26] Update docs/operations/README.md Co-authored-by: Brandon Lenz --- docs/operations/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/operations/README.md b/docs/operations/README.md index 1adee0521..e9d67a5af 100644 --- a/docs/operations/README.md +++ b/docs/operations/README.md @@ -105,7 +105,7 @@ that can take time to learn how to use most effectively. A few hints: - Kibana queries and filters can be used to narrow in on particular environments. Try the query `@source.type:APP` to focus on messages from the - Django application or `@cf.app:"getgov-nmb"` to see results from a single + Django application or `@cf.app:"getgov-{environment}"` to see results from a single environment. Currently, our application emits Python's default log format which is textual From 5c18f05d05a08aaba97e29a67aef215c4ee28ae3 Mon Sep 17 00:00:00 2001 From: Seamus Johnston Date: Fri, 16 Jun 2023 11:02:28 -0500 Subject: [PATCH 22/26] Fix missing comma --- src/.pa11yci | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/.pa11yci b/src/.pa11yci index 4efa264f9..6bb5727e0 100644 --- a/src/.pa11yci +++ b/src/.pa11yci @@ -1,7 +1,7 @@ { "defaults": { "concurrency": 1, - "timeout": 30000, + "timeout": 30000 }, "urls": [ "http://localhost:8080/", From a297ad896330d050e0236b9d419f0a904484dd2c Mon Sep 17 00:00:00 2001 From: Logan McDonald Date: Fri, 16 Jun 2023 09:03:14 -0700 Subject: [PATCH 23/26] remove the 18F sandboxes (#729) * remove the 18F sandboxes * remove 18F team from fixtures --- .github/workflows/deploy-sandbox.yaml | 7 +------ .github/workflows/migrate.yaml | 5 ----- .github/workflows/reset-db.yaml | 5 ----- ops/manifests/manifest-ik.yaml | 29 -------------------------- ops/manifests/manifest-jon.yaml | 29 -------------------------- ops/manifests/manifest-mr.yaml | 29 -------------------------- ops/manifests/manifest-nmb.yaml | 29 -------------------------- ops/manifests/manifest-sspj.yaml | 29 -------------------------- src/registrar/config/settings.py | 5 ----- src/registrar/fixtures.py | 30 --------------------------- 10 files changed, 1 insertion(+), 196 deletions(-) delete mode 100644 ops/manifests/manifest-ik.yaml delete mode 100644 ops/manifests/manifest-jon.yaml delete mode 100644 ops/manifests/manifest-mr.yaml delete mode 100644 ops/manifests/manifest-nmb.yaml delete mode 100644 ops/manifests/manifest-sspj.yaml diff --git a/.github/workflows/deploy-sandbox.yaml b/.github/workflows/deploy-sandbox.yaml index b56db5408..614484a2a 100644 --- a/.github/workflows/deploy-sandbox.yaml +++ b/.github/workflows/deploy-sandbox.yaml @@ -9,12 +9,7 @@ on: jobs: variables: if: | - startsWith(github.head_ref, 'ik/') - || startsWith(github.head_ref, 'jon') - || startsWith(github.head_ref, 'sspj/') - || startsWith(github.head_ref, 'mr/') - || startsWith(github.head_ref, 'nmb/') - || startsWith(github.head_ref, 'ab/') + startsWith(github.head_ref, 'ab/') || startsWith(github.head_ref, 'bl/') || startsWith(github.head_ref, 'rjm/') || startsWith(github.head_ref, 'rb/') diff --git a/.github/workflows/migrate.yaml b/.github/workflows/migrate.yaml index 67ffb4717..574ec9fa0 100644 --- a/.github/workflows/migrate.yaml +++ b/.github/workflows/migrate.yaml @@ -20,11 +20,6 @@ on: - ab - bl - rjm - - jon - - ik - - sspj - - nmb - - mr jobs: migrate: diff --git a/.github/workflows/reset-db.yaml b/.github/workflows/reset-db.yaml index 111314f12..4b9a9eafb 100644 --- a/.github/workflows/reset-db.yaml +++ b/.github/workflows/reset-db.yaml @@ -21,11 +21,6 @@ on: - ab - bl - rjm - - jon - - ik - - sspj - - nmb - - mr jobs: reset-db: diff --git a/ops/manifests/manifest-ik.yaml b/ops/manifests/manifest-ik.yaml deleted file mode 100644 index 60571e9ba..000000000 --- a/ops/manifests/manifest-ik.yaml +++ /dev/null @@ -1,29 +0,0 @@ ---- -applications: -- name: getgov-ik - buildpacks: - - python_buildpack - path: ../../src - instances: 1 - memory: 512M - stack: cflinuxfs4 - timeout: 180 - command: ./run.sh - health-check-type: http - health-check-http-endpoint: /health - env: - # Send stdout and stderr straight to the terminal without buffering - PYTHONUNBUFFERED: yup - # Tell Django where to find its configuration - DJANGO_SETTINGS_MODULE: registrar.config.settings - # Tell Django where it is being hosted - DJANGO_BASE_URL: https://getgov-ik.app.cloud.gov - # Tell Django how much stuff to log - DJANGO_LOG_LEVEL: INFO - # Public site base URL - GETGOV_PUBLIC_SITE_URL: https://federalist-877ab29f-16f6-4f12-961c-96cf064cf070.sites.pages.cloud.gov/site/cisagov/getgov-home/ - routes: - - route: getgov-ik.app.cloud.gov - services: - - getgov-credentials - - getgov-ik-database diff --git a/ops/manifests/manifest-jon.yaml b/ops/manifests/manifest-jon.yaml deleted file mode 100644 index acc226ce3..000000000 --- a/ops/manifests/manifest-jon.yaml +++ /dev/null @@ -1,29 +0,0 @@ ---- -applications: -- name: getgov-jon - buildpacks: - - python_buildpack - path: ../../src - instances: 1 - memory: 512M - stack: cflinuxfs4 - timeout: 180 - command: ./run.sh - health-check-type: http - health-check-http-endpoint: /health - env: - # Send stdout and stderr straight to the terminal without buffering - PYTHONUNBUFFERED: yup - # Tell Django where to find its configuration - DJANGO_SETTINGS_MODULE: registrar.config.settings - # Tell Django where it is being hosted - DJANGO_BASE_URL: https://getgov-jon.app.cloud.gov - # Tell Django how much stuff to log - DJANGO_LOG_LEVEL: INFO - # Public site base URL - GETGOV_PUBLIC_SITE_URL: https://federalist-877ab29f-16f6-4f12-961c-96cf064cf070.sites.pages.cloud.gov/site/cisagov/getgov-home/ - routes: - - route: getgov-jon.app.cloud.gov - services: - - getgov-credentials - - getgov-jon-database diff --git a/ops/manifests/manifest-mr.yaml b/ops/manifests/manifest-mr.yaml deleted file mode 100644 index 2ad39b9ff..000000000 --- a/ops/manifests/manifest-mr.yaml +++ /dev/null @@ -1,29 +0,0 @@ ---- -applications: -- name: getgov-mr - buildpacks: - - python_buildpack - path: ../../src - instances: 1 - memory: 512M - stack: cflinuxfs4 - timeout: 180 - command: ./run.sh - health-check-type: http - health-check-http-endpoint: /health - env: - # Send stdout and stderr straight to the terminal without buffering - PYTHONUNBUFFERED: yup - # Tell Django where to find its configuration - DJANGO_SETTINGS_MODULE: registrar.config.settings - # Tell Django where it is being hosted - DJANGO_BASE_URL: https://getgov-mr.app.cloud.gov - # Tell Django how much stuff to log - DJANGO_LOG_LEVEL: INFO - # Public site base URL - GETGOV_PUBLIC_SITE_URL: https://federalist-877ab29f-16f6-4f12-961c-96cf064cf070.sites.pages.cloud.gov/site/cisagov/getgov-home/ - routes: - - route: getgov-mr.app.cloud.gov - services: - - getgov-credentials - - getgov-mr-database diff --git a/ops/manifests/manifest-nmb.yaml b/ops/manifests/manifest-nmb.yaml deleted file mode 100644 index adea312d5..000000000 --- a/ops/manifests/manifest-nmb.yaml +++ /dev/null @@ -1,29 +0,0 @@ ---- -applications: -- name: getgov-nmb - buildpacks: - - python_buildpack - path: ../../src - instances: 1 - memory: 512M - stack: cflinuxfs4 - timeout: 180 - command: ./run.sh - health-check-type: http - health-check-http-endpoint: /health - env: - # Send stdout and stderr straight to the terminal without buffering - PYTHONUNBUFFERED: yup - # Tell Django where to find its configuration - DJANGO_SETTINGS_MODULE: registrar.config.settings - # Tell Django where it is being hosted - DJANGO_BASE_URL: https://getgov-nmb.app.cloud.gov - # Tell Django how much stuff to log - DJANGO_LOG_LEVEL: INFO - # Public site base URL - GETGOV_PUBLIC_SITE_URL: https://federalist-877ab29f-16f6-4f12-961c-96cf064cf070.sites.pages.cloud.gov/site/cisagov/getgov-home/ - routes: - - route: getgov-nmb.app.cloud.gov - services: - - getgov-credentials - - getgov-nmb-database diff --git a/ops/manifests/manifest-sspj.yaml b/ops/manifests/manifest-sspj.yaml deleted file mode 100644 index cec82c8a1..000000000 --- a/ops/manifests/manifest-sspj.yaml +++ /dev/null @@ -1,29 +0,0 @@ ---- -applications: -- name: getgov-sspj - buildpacks: - - python_buildpack - path: ../../src - instances: 1 - memory: 512M - stack: cflinuxfs4 - timeout: 180 - command: ./run.sh - health-check-type: http - health-check-http-endpoint: /health - env: - # Send stdout and stderr straight to the terminal without buffering - PYTHONUNBUFFERED: yup - # Tell Django where to find its configuration - DJANGO_SETTINGS_MODULE: registrar.config.settings - # Tell Django where it is being hosted - DJANGO_BASE_URL: https://getgov-sspj.app.cloud.gov - # Tell Django how much stuff to log - DJANGO_LOG_LEVEL: INFO - # Public site base URL - GETGOV_PUBLIC_SITE_URL: https://federalist-877ab29f-16f6-4f12-961c-96cf064cf070.sites.pages.cloud.gov/site/cisagov/getgov-home/ - routes: - - route: getgov-sspj.app.cloud.gov - services: - - getgov-credentials - - getgov-sspj-database diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index 29f1689a1..15f8b45a9 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -570,11 +570,6 @@ ALLOWED_HOSTS = [ "getgov-ab.app.cloud.gov", "getgov-bl.app.cloud.gov", "getgov-rjm.app.cloud.gov", - "getgov-jon.app.cloud.gov", - "getgov-mr.app.cloud.gov", - "getgov-sspj.app.cloud.gov", - "getgov-nmb.app.cloud.gov", - "getgov-ik.app.cloud.gov", "get.gov", ] diff --git a/src/registrar/fixtures.py b/src/registrar/fixtures.py index 02a6915aa..1e5f5145e 100644 --- a/src/registrar/fixtures.py +++ b/src/registrar/fixtures.py @@ -24,31 +24,6 @@ class UserFixture: """ ADMINS = [ - { - "username": "c4a0e101-73b4-4d7d-9e5e-7f19a726a0fa", - "first_name": "Seamus", - "last_name": "Johnston", - }, - { - "username": "d4c3bd84-dc3a-48bc-a3c3-f53111df2ec6", - "first_name": "Igor", - "last_name": "", - }, - { - "username": "ee80bfe0-49ad-456d-8d82-e2b608a66517", - "first_name": "Logan", - "last_name": "", - }, - { - "username": "2ffe71b0-cea4-4097-8fb6-7a35b901dd70", - "first_name": "Neil", - "last_name": "Martinsen-Burrell", - }, - { - "username": "7185e6cd-d3c8-4adc-90a3-ceddba71d24f", - "first_name": "Jon", - "last_name": "Roberts", - }, { "username": "5f283494-31bd-49b5-b024-a7e7cae00848", "first_name": "Rachid", @@ -59,11 +34,6 @@ class UserFixture: "first_name": "Alysia", "last_name": "Broddrick", }, - { - "username": "55a3bc26-cd1d-4a5c-a8c0-7e1f561ef7f4", - "first_name": "Michelle", - "last_name": "Rago", - }, { "username": "8f8e7293-17f7-4716-889b-1990241cbd39", "first_name": "Katherine", From 2bd11da866b052260a79af62317a9224f2aad055 Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Fri, 16 Jun 2023 09:10:22 -0700 Subject: [PATCH 24/26] updated documentation with staging addition --- .github/ISSUE_TEMPLATE/developer-onboarding.md | 2 +- docs/developer/database-access.md | 5 ++--- docs/operations/README.md | 4 +++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/developer-onboarding.md b/.github/ISSUE_TEMPLATE/developer-onboarding.md index 74af9ef60..92ae9e3a1 100644 --- a/.github/ISSUE_TEMPLATE/developer-onboarding.md +++ b/.github/ISSUE_TEMPLATE/developer-onboarding.md @@ -83,6 +83,6 @@ export GPG_TTY ## Setting up developer sandbox -We have two types of environments: stable, and sandbox. Stable gets deployed via tagged release every sprint, and developer sandboxes are given to get.gov developers to mess around in a production-like environment without disrupting stable. Each sandbox is namespaced and will automatically be deployed too when the appropriate branch syntax is used for that space in an open pull request. There are several things you need to setup to make the sandbox work for a developer. +We have three types of environments: stable, staging, and sandbox. Stable (production)and staging (pre-prod) get deployed via tagged release, and developer sandboxes are given to get.gov developers to mess around in a production-like environment without disrupting stable or staging. Each sandbox is namespaced and will automatically be deployed too when the appropriate branch syntax is used for that space in an open pull request. There are several things you need to setup to make the sandbox work for a developer. All automation for setting up a developer sandbox is documented in the scripts for [creating a developer sandbox](../../ops/scripts/create_dev_sandbox.sh) and [removing a developer sandbox](../../ops/scripts/destroy_dev_sandbox.sh). A Cloud.gov organization administrator will have to perform the script in order to create the sandbox. diff --git a/docs/developer/database-access.md b/docs/developer/database-access.md index 9d615c477..859ef2fd6 100644 --- a/docs/developer/database-access.md +++ b/docs/developer/database-access.md @@ -42,10 +42,9 @@ Optionally, load data from fixtures as well cf run-task getgov-ENVIRONMENT --wait --command 'python manage.py load' --name loaddata ``` -For the `stable` environment, developers don't have credentials so we need to -run that command using Github Actions. Go to +For the `stable` or `staging` environments, developers don't have credentials so we need to run that command using Github Actions. Go to and select -the "Run workflow" button, making sure that `stable` is selected. +the "Run workflow" button, making sure that `stable` or `staging` depending on which envirornment you desire to update. ## Getting data for fixtures diff --git a/docs/operations/README.md b/docs/operations/README.md index 0f79e3420..ac19c98d7 100644 --- a/docs/operations/README.md +++ b/docs/operations/README.md @@ -36,7 +36,9 @@ Binding the database in `manifest-.json` automatically inserts the # Deploy -We have two types of environments: developer "sandboxes" and `stable`. Developers can deploy locally to their sandbox whenever they want. However, only our CD service can deploy to `stable`, and it does so when we make tagged releases of `main`. This is to ensure that we have a "golden" environment to point to, and can still test things out in a sandbox space. You should make sure all of the USWDS assets are compiled and collected before deploying to your sandbox. To deploy locally to `sandbox`: +We have three types of environments: developer "sandboxes", `staging` and `stable`. Developers can deploy locally to their sandbox whenever they want. However, only our CD service can deploy to `staging` and `stable`, and it does so when we make tagged releases of `main`. For `staging`, this is done to ensure there is a non-production level test envirornment that can be used for user testing or for testing code before it is pushed to `stable`. Whereas `stable` is used to ensure that we have a "golden" environment to point to. We can refer to `stable` as our production environment and `staging` as our pre-production (pre-prod) environment. + +You should make sure all of the USWDS assets are compiled and collected before deploying to your sandbox. To deploy locally to `sandbox`: For ease of use, you can run the `deploy.sh ` script in the `/src` directory to build the assets and deploy to your sandbox. Similarly, you could run `build.sh ` script to just compile and collect the assets without deploying. From 944ddde99e12f4f449cf8f76e5b9909d8726fe56 Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Fri, 16 Jun 2023 09:26:15 -0700 Subject: [PATCH 25/26] added more info about staging --- docs/operations/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/operations/README.md b/docs/operations/README.md index ac19c98d7..2febb86fe 100644 --- a/docs/operations/README.md +++ b/docs/operations/README.md @@ -36,7 +36,7 @@ Binding the database in `manifest-.json` automatically inserts the # Deploy -We have three types of environments: developer "sandboxes", `staging` and `stable`. Developers can deploy locally to their sandbox whenever they want. However, only our CD service can deploy to `staging` and `stable`, and it does so when we make tagged releases of `main`. For `staging`, this is done to ensure there is a non-production level test envirornment that can be used for user testing or for testing code before it is pushed to `stable`. Whereas `stable` is used to ensure that we have a "golden" environment to point to. We can refer to `stable` as our production environment and `staging` as our pre-production (pre-prod) environment. +We have three types of environments: developer "sandboxes", `staging` and `stable`. Developers can deploy locally to their sandbox whenever they want. However, only our CD service can deploy to `staging` and `stable`, and it does so when we make tagged releases of `main`. For `staging`, this is done to ensure there is a non-production level test envirornment that can be used for user testing or for testing code before it is pushed to `stable`. `Staging` can be especially helpful when testing database changes or migrations that could have adververse affects in `stable`. On the other hand, `stable` is used to ensure that we have a "golden" environment to point to. We can refer to `stable` as our production environment and `staging` as our pre-production (pre-prod) environment. As such, code on main should always be tagged for `staging` before it is tagged for `stable`. You should make sure all of the USWDS assets are compiled and collected before deploying to your sandbox. To deploy locally to `sandbox`: From 8e11efc57cd89d71870d057bdc8bbcbbff36703f Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Fri, 16 Jun 2023 10:20:10 -0700 Subject: [PATCH 26/26] Updated to add new sandbox names below staging --- ops/scripts/create_dev_sandbox.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ops/scripts/create_dev_sandbox.sh b/ops/scripts/create_dev_sandbox.sh index 6ff34700e..f180ada8d 100755 --- a/ops/scripts/create_dev_sandbox.sh +++ b/ops/scripts/create_dev_sandbox.sh @@ -43,7 +43,7 @@ cp ops/scripts/manifest-sandbox-template.yaml ops/manifests/manifest-$1.yaml sed -i '' "s/ENVIRONMENT/$1/" "ops/manifests/manifest-$1.yaml" echo "Adding new environment to settings.py..." -sed -i '' '/getgov-stable.app.cloud.gov/ {a\ +sed -i '' '/getgov-staging.app.cloud.gov/ {a\ '\"getgov-$1.app.cloud.gov\"', }' src/registrar/config/settings.py @@ -105,11 +105,11 @@ echo echo "Moving on to setup Github automation..." echo "Adding new environment to Github Actions..." -sed -i '' '/ - stable/ {a\ +sed -i '' '/ - staging/ {a\ - '"$1"' }' .github/workflows/reset-db.yaml -sed -i '' '/ - stable/ {a\ +sed -i '' '/ - staging/ {a\ - '"$1"' }' .github/workflows/migrate.yaml