Merge pull request #1211 from cisagov/rjm/1192-delete-ns-1-2

Issue 1192: Enable the deletion of NS records #1 and #2 if there are enough records in the formset
This commit is contained in:
rachidatecs 2023-10-31 12:10:37 -04:00 committed by GitHub
commit 5c90303c81
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 135 additions and 53 deletions

View file

@ -273,31 +273,35 @@ function prepareDeleteButtons(formLabel) {
// h2 and legend for DS form, label for nameservers // h2 and legend for DS form, label for nameservers
Array.from(form.querySelectorAll('h2, legend, label, p')).forEach((node) => { Array.from(form.querySelectorAll('h2, legend, label, p')).forEach((node) => {
// Ticket: 1192 // If the node is a nameserver label, one of the first 2 which was previously 3 and up (not required)
// if (isNameserversForm && index <= 1 && !node.innerHTML.includes('*')) { // inject the USWDS required markup and make sure the INPUT is required
// // Create a new element if (isNameserversForm && index <= 1 && node.innerHTML.includes('server') && !node.innerHTML.includes('*')) {
// const newElement = document.createElement('abbr'); // Create a new element
// newElement.textContent = '*'; const newElement = document.createElement('abbr');
// // TODO: finish building abbr newElement.textContent = '*';
newElement.setAttribute("title", "required");
newElement.classList.add("usa-hint", "usa-hint--required");
// // Append the new element to the parent // Append the new element to the label
// node.appendChild(newElement); node.appendChild(newElement);
// // Find the next sibling that is an input element // Find the next sibling that is an input element
// let nextInputElement = node.nextElementSibling; let nextInputElement = node.nextElementSibling;
// while (nextInputElement) { while (nextInputElement) {
// if (nextInputElement.tagName === 'INPUT') { if (nextInputElement.tagName === 'INPUT') {
// // Found the next input element // Found the next input element
// console.log(nextInputElement); nextInputElement.setAttribute("required", "")
// break; break;
// } }
// nextInputElement = nextInputElement.nextElementSibling; nextInputElement = nextInputElement.nextElementSibling;
// } }
// nextInputElement.required = true; nextInputElement.required = true;
// } }
// Ticket: 1192 - remove if let innerSpan = node.querySelector('span')
if (!(isNameserversForm && index <= 1)) { if (innerSpan) {
innerSpan.textContent = innerSpan.textContent.replace(formLabelRegex, `${formLabel} ${index + 1}`);
} else {
node.textContent = node.textContent.replace(formLabelRegex, `${formLabel} ${index + 1}`); node.textContent = node.textContent.replace(formLabelRegex, `${formLabel} ${index + 1}`);
node.textContent = node.textContent.replace(formExampleRegex, `ns${index + 1}`); node.textContent = node.textContent.replace(formExampleRegex, `ns${index + 1}`);
} }
@ -305,7 +309,15 @@ function prepareDeleteButtons(formLabel) {
// Display the add more button if we have less than 13 forms // Display the add more button if we have less than 13 forms
if (isNameserversForm && forms.length <= 13) { if (isNameserversForm && forms.length <= 13) {
addButton.classList.remove("display-none") console.log('remove disabled');
addButton.removeAttribute("disabled");
}
if (isNameserversForm && forms.length < 3) {
// Hide the delete buttons on the remaining nameservers
Array.from(form.querySelectorAll('.delete-record')).forEach((deleteButton) => {
deleteButton.setAttribute("disabled", "true");
});
} }
}); });
@ -333,6 +345,11 @@ function prepareDeleteButtons(formLabel) {
formLabel = "DS Data record"; formLabel = "DS Data record";
} }
// On load: Disable the add more button if we have 13 forms
if (isNameserversForm && document.querySelectorAll(".repeatable-form").length == 13) {
addButton.setAttribute("disabled", "true");
}
// Attach click event listener on the delete buttons of the existing forms // Attach click event listener on the delete buttons of the existing forms
prepareDeleteButtons(formLabel); prepareDeleteButtons(formLabel);
@ -348,6 +365,33 @@ function prepareDeleteButtons(formLabel) {
// For the eample on Nameservers // For the eample on Nameservers
let formExampleRegex = RegExp(`ns(\\d){1}`, 'g'); let formExampleRegex = RegExp(`ns(\\d){1}`, 'g');
// Some Nameserver form checks since the delete can mess up the source object we're copying
// in regards to required fields and hidden delete buttons
if (isNameserversForm) {
// If the source element we're copying has required on an input,
// reset that input
let formRequiredNeedsCleanUp = newForm.innerHTML.includes('*');
if (formRequiredNeedsCleanUp) {
newForm.querySelector('label abbr').remove();
// Get all input elements within the container
const inputElements = newForm.querySelectorAll("input");
// Loop through each input element and remove the 'required' attribute
inputElements.forEach((input) => {
if (input.hasAttribute("required")) {
input.removeAttribute("required");
}
});
}
// If the source element we're copying has an disabled delete button,
// enable that button
let deleteButton= newForm.querySelector('.delete-record');
if (deleteButton.hasAttribute("disabled")) {
deleteButton.removeAttribute("disabled");
}
}
formNum++; formNum++;
newForm.innerHTML = newForm.innerHTML.replace(formNumberRegex, `form-${formNum-1}-`); newForm.innerHTML = newForm.innerHTML.replace(formNumberRegex, `form-${formNum-1}-`);
newForm.innerHTML = newForm.innerHTML.replace(formLabelRegex, `${formLabel} ${formNum}`); newForm.innerHTML = newForm.innerHTML.replace(formLabelRegex, `${formLabel} ${formNum}`);
@ -397,9 +441,18 @@ function prepareDeleteButtons(formLabel) {
// Attach click event listener on the delete buttons of the new form // Attach click event listener on the delete buttons of the new form
prepareDeleteButtons(formLabel); prepareDeleteButtons(formLabel);
// Hide the add more button if we have 13 forms // Disable the add more button if we have 13 forms
if (isNameserversForm && formNum == 13) { if (isNameserversForm && formNum == 13) {
addButton.classList.add("display-none") addButton.setAttribute("disabled", "true");
}
if (isNameserversForm && forms.length >= 2) {
// Enable the delete buttons on the nameservers
forms.forEach((form, index) => {
Array.from(form.querySelectorAll('.delete-record')).forEach((deleteButton) => {
deleteButton.removeAttribute("disabled");
});
});
} }
} }
})(); })();

View file

@ -23,11 +23,6 @@ class DomainAddUserForm(forms.Form):
email = forms.EmailField(label="Email") email = forms.EmailField(label="Email")
class IPAddressField(forms.CharField):
def validate(self, value):
super().validate(value) # Run the default CharField validation
class DomainNameserverForm(forms.Form): class DomainNameserverForm(forms.Form):
"""Form for changing nameservers.""" """Form for changing nameservers."""
@ -35,7 +30,21 @@ class DomainNameserverForm(forms.Form):
server = forms.CharField(label="Name server", strip=True) server = forms.CharField(label="Name server", strip=True)
ip = forms.CharField(label="IP Address (IPv4 or IPv6)", strip=True, required=False) ip = forms.CharField(
label="IP address (IPv4 or IPv6)",
strip=True,
required=False,
)
def __init__(self, *args, **kwargs):
super(DomainNameserverForm, self).__init__(*args, **kwargs)
# add custom error messages
self.fields["server"].error_messages.update(
{
"required": "A minimum of 2 name servers are required.",
}
)
def clean(self): def clean(self):
# clean is called from clean_forms, which is called from is_valid # clean is called from clean_forms, which is called from is_valid

View file

@ -2,8 +2,12 @@
class="{% if label_classes %} {{ label_classes }}{% endif %}{% if label_tag == 'legend' %} {{ legend_classes }}{% endif %}" class="{% if label_classes %} {{ label_classes }}{% endif %}{% if label_tag == 'legend' %} {{ legend_classes }}{% endif %}"
{% if not field.use_fieldset %}for="{{ widget.attrs.id }}"{% endif %} {% if not field.use_fieldset %}for="{{ widget.attrs.id }}"{% endif %}
> >
{{ field.label }} {% if span_for_text %}
{% if widget.attrs.required %} <span>{{ field.label }}</span>
{% else %}
{{ field.label }}
{% endif %}
{% if widget.attrs.required %}
<abbr class="usa-hint usa-hint--required" title="required">*</abbr> <abbr class="usa-hint usa-hint--required" title="required">*</abbr>
{% endif %} {% endif %}
</{{ label_tag }}> </{{ label_tag }}>

View file

@ -35,11 +35,14 @@
{{ form.domain }} {{ form.domain }}
{% with sublabel_text="Example: ns"|concat:forloop.counter|concat:".example.com" %} {% with sublabel_text="Example: ns"|concat:forloop.counter|concat:".example.com" %}
{% if forloop.counter <= 2 %} {% if forloop.counter <= 2 %}
{% with attr_required=True add_group_class="usa-form-group--unstyled-error" %} {# span_for_text will wrap the copy in s <span>, which we'll use in the JS for this component #}
{% with attr_required=True add_group_class="usa-form-group--unstyled-error" span_for_text=True %}
{% input_with_errors form.server %} {% input_with_errors form.server %}
{% endwith %} {% endwith %}
{% else %} {% else %}
{% with span_for_text=True %}
{% input_with_errors form.server %} {% input_with_errors form.server %}
{% endwith %}
{% endif %} {% endif %}
{% endwith %} {% endwith %}
</div> </div>
@ -49,14 +52,11 @@
{% endwith %} {% endwith %}
</div> </div>
<div class="tablet:grid-col-2"> <div class="tablet:grid-col-2">
{% comment %} TODO: remove this if for 1192 {% endcomment %}
{% if forloop.counter > 2 %}
<button type="button" class="usa-button usa-button--unstyled display-block delete-record margin-bottom-075"> <button type="button" class="usa-button usa-button--unstyled display-block delete-record margin-bottom-075">
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24"> <svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
<use xlink:href="{%static 'img/sprite.svg'%}#delete"></use> <use xlink:href="{%static 'img/sprite.svg'%}#delete"></use>
</svg><span class="margin-left-05">Delete</span> </svg><span class="margin-left-05">Delete</span>
</button> </button>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
@ -81,7 +81,7 @@
type="submit" type="submit"
class="usa-button usa-button--outline" class="usa-button usa-button--outline"
name="btn-cancel-click" name="btn-cancel-click"
aria-label="Reset the data in the Name Server form to the registry state (undo changes)" aria-label="Reset the data in the name server form to the registry state (undo changes)"
>Cancel >Cancel
</button> </button>
</div> </div>

View file

@ -1464,7 +1464,12 @@ class TestDomainNameservers(TestDomainOverview):
# form submission was a post with an error, response should be a 200 # form submission was a post with an error, response should be a 200
# error text appears twice, once at the top of the page, once around # error text appears twice, once at the top of the page, once around
# the required field. form requires a minimum of 2 name servers # the required field. form requires a minimum of 2 name servers
self.assertContains(result, "This field is required.", count=2, status_code=200) self.assertContains(
result,
"A minimum of 2 name servers are required.",
count=2,
status_code=200,
)
def test_domain_nameservers_form_submit_subdomain_missing_ip(self): def test_domain_nameservers_form_submit_subdomain_missing_ip(self):
"""Nameserver form catches missing ip error on subdomain. """Nameserver form catches missing ip error on subdomain.
@ -1632,7 +1637,12 @@ class TestDomainNameservers(TestDomainOverview):
# form submission was a post with an error, response should be a 200 # form submission was a post with an error, response should be a 200
# error text appears four times, twice at the top of the page, # error text appears four times, twice at the top of the page,
# once around each required field. # once around each required field.
self.assertContains(result, "This field is required", count=4, status_code=200) self.assertContains(
result,
"A minimum of 2 name servers are required.",
count=4,
status_code=200,
)
class TestDomainAuthorizingOfficial(TestDomainOverview): class TestDomainAuthorizingOfficial(TestDomainOverview):
@ -1800,7 +1810,11 @@ class TestDomainSecurityEmail(TestDomainOverview):
( (
"RegistryError", "RegistryError",
form_data_registry_error, form_data_registry_error,
"Update failed. Cannot contact the registry.", """
Were experiencing a system connection error. Please wait a few minutes
and try again. If you continue to receive this error after a few tries,
contact help@get.gov
""",
), ),
("ContactError", form_data_contact_error, "Value entered was wrong."), ("ContactError", form_data_contact_error, "Value entered was wrong."),
( (
@ -1835,7 +1849,7 @@ class TestDomainSecurityEmail(TestDomainOverview):
self.assertEqual(len(messages), 1) self.assertEqual(len(messages), 1)
message = messages[0] message = messages[0]
self.assertEqual(message.tags, message_tag) self.assertEqual(message.tags, message_tag)
self.assertEqual(message.message, expected_message) self.assertEqual(message.message.strip(), expected_message.strip())
def test_domain_overview_blocked_for_ineligible_user(self): def test_domain_overview_blocked_for_ineligible_user(self):
"""We could easily duplicate this test for all domain management """We could easily duplicate this test for all domain management

View file

@ -39,9 +39,11 @@ class GenericError(Exception):
""" """
_error_mapping = { _error_mapping = {
GenericErrorCodes.CANNOT_CONTACT_REGISTRY: ( GenericErrorCodes.CANNOT_CONTACT_REGISTRY: """
"Update failed. Cannot contact the registry." Were experiencing a system connection error. Please wait a few minutes
), and try again. If you continue to receive this error after a few tries,
contact help@get.gov
""",
GenericErrorCodes.GENERIC_ERROR: ("Value entered was wrong."), GenericErrorCodes.GENERIC_ERROR: ("Value entered was wrong."),
} }