From 97980cbdf6684c252c658bae2170c443fa464ee1 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Wed, 29 Sep 2021 14:33:02 -0500 Subject: [PATCH 01/21] Failing test for setting project allocation to 0.125 --- tock/hours/tests/test_forms.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tock/hours/tests/test_forms.py b/tock/hours/tests/test_forms.py index 444370c9..5a3093d0 100644 --- a/tock/hours/tests/test_forms.py +++ b/tock/hours/tests/test_forms.py @@ -365,3 +365,10 @@ def test_one_project_with_notes_and_one_without_notes_is_valid(self): form_data['timecardobjects-0-notes'] = 'Did some work.' formset = TimecardFormSet(form_data, instance=self.timecard) self.assertTrue(formset.is_valid()) + + def test_smallest_project_allocation(self): + """Should be able to make a timecard with 12.5% project allocation""" + form_data = self.form_data() + form_data['timecardobjects-0-project_allocation'] = '0.125' + formset = TimecardFormSet(form_data, instance=self.timecard) + self.assertTrue(formset.is_valid()) From 15c6b32992d9ef2b4c887509a2077dc73cb3c0fd Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Wed, 29 Sep 2021 14:39:55 -0500 Subject: [PATCH 02/21] Allow more decimal places for allocation --- .../migrations/0037_small_allocation.py | 18 ++++++++++++++++++ tock/employees/models.py | 4 ++-- tock/hours/migrations/0063_small_allocation.py | 18 ++++++++++++++++++ tock/hours/models.py | 4 ++-- 4 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 tock/employees/migrations/0037_small_allocation.py create mode 100644 tock/hours/migrations/0063_small_allocation.py diff --git a/tock/employees/migrations/0037_small_allocation.py b/tock/employees/migrations/0037_small_allocation.py new file mode 100644 index 00000000..f4453f75 --- /dev/null +++ b/tock/employees/migrations/0037_small_allocation.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-09-29 19:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('employees', '0036_alter_userdata_expected_project_allocation'), + ] + + operations = [ + migrations.AlterField( + model_name='userdata', + name='expected_project_allocation', + field=models.DecimalField(blank=True, decimal_places=3, default=1.0, help_text='Enter in Decimal Format (ex. 1.00 = 100%, 0.50 = 50%)', max_digits=6, null=True, verbose_name='Expected Project Allocation'), + ), + ] diff --git a/tock/employees/models.py b/tock/employees/models.py index 08875811..1bc053fa 100644 --- a/tock/employees/models.py +++ b/tock/employees/models.py @@ -85,8 +85,8 @@ class UserData(models.Model): default=settings.DEFAULT_EXPECTED_BILLABLE_HOURS, help_text="Number of hours expected to be billable in a 40 hour work week") expected_project_allocation = models.DecimalField( - decimal_places=2, - max_digits=5, + decimal_places=3, + max_digits=6, blank=True, null=True, default=settings.DEFAULT_EXPECTED_PROJECT_ALLOCATION, diff --git a/tock/hours/migrations/0063_small_allocation.py b/tock/hours/migrations/0063_small_allocation.py new file mode 100644 index 00000000..6cbb6acd --- /dev/null +++ b/tock/hours/migrations/0063_small_allocation.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-09-29 19:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hours', '0062_timecardobject_project_allocation'), + ] + + operations = [ + migrations.AlterField( + model_name='timecardobject', + name='project_allocation', + field=models.DecimalField(choices=[(0, '---'), (1.0, '100%'), (0.5, '50%'), (0.25, '25%'), (0.125, '12.5%')], decimal_places=3, default=0, max_digits=6), + ), + ] diff --git a/tock/hours/models.py b/tock/hours/models.py index 06b3400a..412d55ad 100644 --- a/tock/hours/models.py +++ b/tock/hours/models.py @@ -476,8 +476,8 @@ class TimecardObject(models.Model): created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) grade = models.ForeignKey(EmployeeGrade, blank=True, null=True, on_delete=models.CASCADE) - project_allocation = models.DecimalField(choices=settings.PROJECT_ALLOCATION_CHOICES, default=0, decimal_places=2, - max_digits=5) + project_allocation = models.DecimalField(choices=settings.PROJECT_ALLOCATION_CHOICES, default=0, decimal_places=3, + max_digits=6) # The notes field is where the user records notes about time spent on # certain projects (for example, time spent on general projects). It may # only be display and required when certain projects are selected. From 687a5368954e4614ad0714ba1ea570ee1602f569 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Thu, 30 Sep 2021 12:11:33 -0500 Subject: [PATCH 03/21] Split migration to fix deploy to staging --- tock/hours/migrations/0063_small_allocation.py | 2 +- .../hours/migrations/0064_small_allocation2.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tock/hours/migrations/0064_small_allocation2.py diff --git a/tock/hours/migrations/0063_small_allocation.py b/tock/hours/migrations/0063_small_allocation.py index 6cbb6acd..e6ad82cf 100644 --- a/tock/hours/migrations/0063_small_allocation.py +++ b/tock/hours/migrations/0063_small_allocation.py @@ -13,6 +13,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='timecardobject', name='project_allocation', - field=models.DecimalField(choices=[(0, '---'), (1.0, '100%'), (0.5, '50%'), (0.25, '25%'), (0.125, '12.5%')], decimal_places=3, default=0, max_digits=6), + field=models.DecimalField(choices=[(0, '---'), (1.0, '100%'), (0.5, '50%'), (0.25, '25%'), (0.125, '12.5%')], decimal_places=2, default=0, max_digits=5), ), ] diff --git a/tock/hours/migrations/0064_small_allocation2.py b/tock/hours/migrations/0064_small_allocation2.py new file mode 100644 index 00000000..7be18d0a --- /dev/null +++ b/tock/hours/migrations/0064_small_allocation2.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-09-30 17:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hours', '0063_small_allocation'), + ] + + operations = [ + migrations.AlterField( + model_name='timecardobject', + name='project_allocation', + field=models.DecimalField(choices=[(0, '---'), (1.0, '100%'), (0.5, '50%'), (0.25, '25%'), (0.125, '12.5%')], decimal_places=3, default=0, max_digits=6), + ), + ] From 866c63524398113a0dcb0e20a6ec56e9db15a5a0 Mon Sep 17 00:00:00 2001 From: Caley Woods Date: Fri, 1 Oct 2021 13:33:29 -0500 Subject: [PATCH 04/21] Fix server error and hour validation for weekly billing When attempting to submit a timecard from the admin interface if you left the "Hours Spent" column completely blank and didn't enter a zero (0) in the process of adding a weekly billing project it would cause the server to throw a TypeError. There is now a guard statement in the admin code to prevent this. Additionally, the hourly validation logic was still being applied to TimecardObjects even if there was a weekly billing project on the Timecard. Now we don't throw the hourly validation warning if a weekly billing project is detected on the Timecard. --- tock/hours/admin.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tock/hours/admin.py b/tock/hours/admin.py index e22d5492..8711fcea 100644 --- a/tock/hours/admin.py +++ b/tock/hours/admin.py @@ -37,6 +37,7 @@ def clean(self): return hours = Decimal(0.0) + weekly_billing_found = False aws_eligible = UserData.objects.get( user__id=self.instance.user_id).is_aws_eligible min_working_hours = self.instance.reporting_period.min_working_hours @@ -44,7 +45,12 @@ def clean(self): for unit in self.cleaned_data: try: - hours = hours + unit['hours_spent'] + if unit['hours_spent']: + hours = hours + unit['hours_spent'] + else: + if (unit['project_allocation'] and unit['project_allocation'] > 0): + weekly_billing_found = True + break except KeyError: pass @@ -53,7 +59,7 @@ def clean(self): 'You have entered more than %s hours' % max_working_hours ) - if hours < min_working_hours and not aws_eligible: + if hours < min_working_hours and not aws_eligible and not weekly_billing_found: raise ValidationError( 'You have entered fewer than %s hours' % min_working_hours ) From 8e8a36e63368985bbe32f1eba84090b145c41acd Mon Sep 17 00:00:00 2001 From: Caley Woods Date: Fri, 1 Oct 2021 13:37:03 -0500 Subject: [PATCH 05/21] Admin panel weekly billing tests --- tock/hours/tests/test_integration.py | 29 +++++++++++++++++++++++++++- tock/projects/fixtures/projects.json | 10 ++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/tock/hours/tests/test_integration.py b/tock/hours/tests/test_integration.py index 47b128cc..35ff5a70 100644 --- a/tock/hours/tests/test_integration.py +++ b/tock/hours/tests/test_integration.py @@ -25,7 +25,7 @@ def setUp(self): ) self.projects = [ projects.models.Project.objects.get(name='openFEC'), - projects.models.Project.objects.get(name='Peace Corps'), + projects.models.Project.objects.get(name='Peace Corps') ] self.reporting_period = hours.models.ReportingPeriod.objects.create( start_date=datetime.date(2015, 1, 1), @@ -45,6 +45,11 @@ def setUp(self): user=self.user, reporting_period=self.reporting_period2, ) + self.reporting_period3 = hours.models.ReportingPeriod.objects.create( + start_date=datetime.date(2015, 1, 15), + end_date=datetime.date(2015, 1, 21), + exact_working_hours=40, + ) def _assert_project_options(self, positive=None, negative=None): """Browse to timecard update page, then assert that positive options are @@ -155,3 +160,25 @@ def test_timecard_submit_twice(self): # successful POST will give a 302 redirect self.assertEqual(res.status_code, 302) + +class TestAdmin(ProtectedViewTestCase, WebTest): + + fixtures = [ + 'projects/fixtures/projects.json' + ] + + setUp = TestOptions.setUp + + def test_admin_weekly_bill_timecard_submit(self): + """Test a weekly billed project via the admin interface""" + weekly_billed_project = projects.models.Project.objects.get(name='Weekly Billing') + url = reverse('admin:hours_timecard_add') + res = self.app.get(url, user=self.user) + form = res.form + form["user"] = self.user.id + form["reporting_period"] = "3" + form["timecardobjects-0-project"] = weekly_billed_project.id + form["timecardobjects-0-project_allocation"] = "1.0" + res = form.submit("submit-timecard").follow() + self.assertEqual(res.status_code, 200) + self.assertContains(res, "was added successfully") diff --git a/tock/projects/fixtures/projects.json b/tock/projects/fixtures/projects.json index 05d558bb..2aa47b05 100644 --- a/tock/projects/fixtures/projects.json +++ b/tock/projects/fixtures/projects.json @@ -684,5 +684,15 @@ "accounting_code":15 }, "pk":50 + }, + { + "model":"projects.project", + "fields":{ + "name":"Weekly Billing", + "description":"", + "accounting_code":15, + "is_weekly_bill": true + }, + "pk":51 } ] From 3b0726f03d26045f12a86546c2144168f201bd8a Mon Sep 17 00:00:00 2001 From: Caley Woods Date: Fri, 1 Oct 2021 13:44:02 -0500 Subject: [PATCH 06/21] Fix test case reporting period option When all tests run the reporting period options grow. Perhaps there is a better way to select the reporting period we want? --- tock/hours/tests/test_integration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tock/hours/tests/test_integration.py b/tock/hours/tests/test_integration.py index 35ff5a70..7b521447 100644 --- a/tock/hours/tests/test_integration.py +++ b/tock/hours/tests/test_integration.py @@ -176,7 +176,7 @@ def test_admin_weekly_bill_timecard_submit(self): res = self.app.get(url, user=self.user) form = res.form form["user"] = self.user.id - form["reporting_period"] = "3" + form["reporting_period"] = "49" form["timecardobjects-0-project"] = weekly_billed_project.id form["timecardobjects-0-project_allocation"] = "1.0" res = form.submit("submit-timecard").follow() From 011ee30bb65b48b73ac8a85e841a3500ffa42b20 Mon Sep 17 00:00:00 2001 From: Caley Woods Date: Wed, 6 Oct 2021 13:37:45 -0500 Subject: [PATCH 07/21] Attempt to handle FY boundaries --- tock/utilization/tests/test_views.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/tock/utilization/tests/test_views.py b/tock/utilization/tests/test_views.py index 204fabfc..3e988a66 100644 --- a/tock/utilization/tests/test_views.py +++ b/tock/utilization/tests/test_views.py @@ -43,7 +43,7 @@ def setUp(self): non_billable_project.accounting_code = non_billable_acct non_billable_project.save() - billable_project = Project.objects.last() + billable_project = Project.objects.get(pk=50) billable_project.accounting_code = billable_acct billable_project.save() @@ -60,8 +60,23 @@ def setUp(self): self.user_data.unit = test_unit self.user_data.save() + # These utilization tests get weird around fiscal years, this is an attempt + # to handle things better inside of the first week to 10 days of October + today = datetime.date.today() + current_fy = ReportingPeriod().get_fiscal_year_from_date(today) + fy_start_date = ReportingPeriod().get_fiscal_year_start_date(current_fy) + first_of_october = datetime.date(today.year, 10, 1) + seventh_of_october = datetime.date(today.year, 10, 7) + adjust_rp_start_date_for_fy_boundary = first_of_october <= today <= seventh_of_october + rp_start_date = datetime.date.today() - datetime.timedelta(days=7) + + # We think we're within the first week of October and rewinding back 7 days would cross + # a FY boundary, start from the beginning of the FY instead + if adjust_rp_start_date_for_fy_boundary: + rp_start_date = fy_start_date + self.reporting_period = ReportingPeriod.objects.create( - start_date=datetime.date.today() - datetime.timedelta(days=7), + start_date=rp_start_date, end_date=datetime.date.today() ) From 1525d2bf2d857410c71ae1a042ffd1d8a1a11641 Mon Sep 17 00:00:00 2001 From: Caley Woods Date: Wed, 6 Oct 2021 13:55:15 -0500 Subject: [PATCH 08/21] Better handling of 'safe' date --- tock/utilization/tests/test_views.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tock/utilization/tests/test_views.py b/tock/utilization/tests/test_views.py index 3e988a66..ff2ebc58 100644 --- a/tock/utilization/tests/test_views.py +++ b/tock/utilization/tests/test_views.py @@ -65,13 +65,10 @@ def setUp(self): today = datetime.date.today() current_fy = ReportingPeriod().get_fiscal_year_from_date(today) fy_start_date = ReportingPeriod().get_fiscal_year_start_date(current_fy) - first_of_october = datetime.date(today.year, 10, 1) - seventh_of_october = datetime.date(today.year, 10, 7) - adjust_rp_start_date_for_fy_boundary = first_of_october <= today <= seventh_of_october + safe_date = fy_start_date + datetime.timedelta(days=7) + adjust_rp_start_date_for_fy_boundary = today < safe_date rp_start_date = datetime.date.today() - datetime.timedelta(days=7) - # We think we're within the first week of October and rewinding back 7 days would cross - # a FY boundary, start from the beginning of the FY instead if adjust_rp_start_date_for_fy_boundary: rp_start_date = fy_start_date From b22725b602f0e013f407e9b0c72bd68e73725d10 Mon Sep 17 00:00:00 2001 From: Caley Woods Date: Wed, 6 Oct 2021 16:22:22 -0500 Subject: [PATCH 09/21] Update project_allocation API scale/precision --- tock/api/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tock/api/views.py b/tock/api/views.py index 92d0bf98..df13d5be 100644 --- a/tock/api/views.py +++ b/tock/api/views.py @@ -87,7 +87,7 @@ class TimecardSerializer(serializers.Serializer): allow_null=True ) hours_spent = serializers.DecimalField(max_digits=5, decimal_places=2) - project_allocation = serializers.DecimalField(max_digits=5, decimal_places=2) + project_allocation = serializers.DecimalField(max_digits=6, decimal_places=3) start_date = serializers.DateField( source='timecard.reporting_period.start_date' ) From 5b36aa73ab5794a17eb71b1923d650b6ce4d8a82 Mon Sep 17 00:00:00 2001 From: Caley Woods Date: Thu, 7 Oct 2021 09:02:45 -0500 Subject: [PATCH 10/21] Fix static method calls --- tock/utilization/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tock/utilization/utils.py b/tock/utilization/utils.py index cb462493..dce59d18 100644 --- a/tock/utilization/utils.py +++ b/tock/utilization/utils.py @@ -77,8 +77,8 @@ def limit_to_fy(): """ Filter component to Limit timecard aggregation to the current fiscal year """ - current_fy = ReportingPeriod().get_fiscal_year_from_date(datetime.date.today()) - fy_start_date = ReportingPeriod().get_fiscal_year_start_date(current_fy) + current_fy = ReportingPeriod.get_fiscal_year_from_date(datetime.date.today()) + fy_start_date = ReportingPeriod.get_fiscal_year_start_date(current_fy) return Q(timecards__submitted=True, timecards__reporting_period__start_date__gte=fy_start_date) def _get_reporting_periods(count): From dca278fe8f5d12a7a806fac4b7e353f1fa3c62fc Mon Sep 17 00:00:00 2001 From: Caley Woods Date: Thu, 7 Oct 2021 09:03:01 -0500 Subject: [PATCH 11/21] Code clarity changes --- tock/utilization/tests/test_views.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tock/utilization/tests/test_views.py b/tock/utilization/tests/test_views.py index ff2ebc58..54cf6aff 100644 --- a/tock/utilization/tests/test_views.py +++ b/tock/utilization/tests/test_views.py @@ -63,14 +63,15 @@ def setUp(self): # These utilization tests get weird around fiscal years, this is an attempt # to handle things better inside of the first week to 10 days of October today = datetime.date.today() - current_fy = ReportingPeriod().get_fiscal_year_from_date(today) - fy_start_date = ReportingPeriod().get_fiscal_year_start_date(current_fy) + current_fy = ReportingPeriod.get_fiscal_year_from_date(today) + fy_start_date = ReportingPeriod.get_fiscal_year_start_date(current_fy) safe_date = fy_start_date + datetime.timedelta(days=7) adjust_rp_start_date_for_fy_boundary = today < safe_date - rp_start_date = datetime.date.today() - datetime.timedelta(days=7) if adjust_rp_start_date_for_fy_boundary: rp_start_date = fy_start_date + else: + rp_start_date = datetime.date.today() - datetime.timedelta(days=7) self.reporting_period = ReportingPeriod.objects.create( start_date=rp_start_date, From a8ab7b7c25b9abc1f452bdc0341915c768ebb2fc Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Thu, 7 Oct 2021 14:27:55 -0500 Subject: [PATCH 12/21] Failing test for project allocation --- tock/hours/tests/test_integration.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tock/hours/tests/test_integration.py b/tock/hours/tests/test_integration.py index 7b521447..e6efc00e 100644 --- a/tock/hours/tests/test_integration.py +++ b/tock/hours/tests/test_integration.py @@ -182,3 +182,24 @@ def test_admin_weekly_bill_timecard_submit(self): res = form.submit("submit-timecard").follow() self.assertEqual(res.status_code, 200) self.assertContains(res, "was added successfully") + + def test_admin_weekly_bill_project_allocation(self): + """Test project allocation in the admin interface""" + weekly_billed_project = projects.models.Project.objects.get(name='Weekly Billing') + timecard = hours.models.Timecard.objects.first() + change_url = reverse( + 'admin:hours_timecard_change', + args=[timecard.id], + ) + + # save a timecard with project allocation set. + form = self.app.get(change_url, user=self.user).form + form["timecardobjects-0-project"] = weekly_billed_project.id + form["timecardobjects-0-hours_spent"] = '' + form["timecardobjects-0-project_allocation"] = "1.0" + form.submit("submit-timecard") + + # now visit the change page and make sure that 100% is selected + change_page = self.app.get(change_url, user=self.user) + form = change_page.form + self.assertEqual(form["timecardobjects-0-project_allocation"].value, "1.0") From f216d50bf2d3852fd7fac39236fd7a80d9c7de97 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Wed, 29 Sep 2021 15:55:04 -0500 Subject: [PATCH 13/21] New decimal choice widget --- tock/hours/admin.py | 63 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tock/hours/admin.py b/tock/hours/admin.py index 8711fcea..c84cb8f7 100644 --- a/tock/hours/admin.py +++ b/tock/hours/admin.py @@ -1,9 +1,11 @@ from decimal import Decimal +from math import isclose from django.conf import settings from django.contrib import admin from django.core.exceptions import ValidationError from django.forms import DecimalField, ModelForm +from django.forms.widgets import Select from django.forms.models import BaseInlineFormSet from employees.models import UserData @@ -24,8 +26,68 @@ def queryset(self, request, queryset): return queryset +class DecimalChoiceWidget(Select): + + """A choice widget for decimal typed data. + + The Select widget when used with a form's TypedChoiceField that has an + underlying `DecimalField` in the model is very sensitive to formatting because + it uses string comparison (as is natural for a Select widget on a web page that + deals in strings). + + This modifies the method in a `Select` widget where equality is checked so that + it uses numerical equality. This should make it much better behaved with the + numeric data type. + """ + + def optgroups(self, name, value, attrs=None): + """Change the baseclass's method to use numerical comparison.""" + groups = [] + has_selected = False + + for index, (option_value, option_label) in enumerate(self.choices): + if option_value is None: + option_value = '' + + subgroup = [] + if isinstance(option_label, (list, tuple)): + group_name = option_value + subindex = 0 + choices = option_label + else: + group_name = None + subindex = None + choices = [(option_value, option_label)] + groups.append((group_name, subgroup, index)) + + for subvalue, sublabel in choices: + selected = ( + # instead of string comparison, use numerical comparison here + any(isclose(float(subvalue), float(v)) for v in value) and + (not has_selected or self.allow_multiple_selected) + ) + has_selected |= selected + subgroup.append(self.create_option( + name, subvalue, sublabel, selected, index, + subindex=subindex, attrs=attrs, + )) + if subindex is not None: + subindex += 1 + return groups + + +class TimecardObjectForm(ModelForm): + + class Meta: + model = TimecardObject + fields = "__all__" + widgets = {"project_allocation": DecimalChoiceWidget} + class TimecardObjectFormset(BaseInlineFormSet): + + form = TimecardObjectForm + def clean(self): """ Check to ensure the proper number of hours are entered. @@ -73,6 +135,7 @@ class ReportingPeriodAdmin(admin.ModelAdmin): class TimecardObjectInline(admin.TabularInline): formset = TimecardObjectFormset + form = TimecardObjectForm model = TimecardObject readonly_fields = [ 'grade', From 29bac95cc55cfdeb31b2405c660f7e28a732ce42 Mon Sep 17 00:00:00 2001 From: Caley Woods Date: Wed, 6 Oct 2021 16:22:22 -0500 Subject: [PATCH 14/21] Update project_allocation API scale/precision --- tock/api/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tock/api/views.py b/tock/api/views.py index 92d0bf98..df13d5be 100644 --- a/tock/api/views.py +++ b/tock/api/views.py @@ -87,7 +87,7 @@ class TimecardSerializer(serializers.Serializer): allow_null=True ) hours_spent = serializers.DecimalField(max_digits=5, decimal_places=2) - project_allocation = serializers.DecimalField(max_digits=5, decimal_places=2) + project_allocation = serializers.DecimalField(max_digits=6, decimal_places=3) start_date = serializers.DateField( source='timecard.reporting_period.start_date' ) From 8a03470fa737984a2286b56a73a2d8bee9d9bca5 Mon Sep 17 00:00:00 2001 From: Caley Woods Date: Thu, 7 Oct 2021 16:01:28 -0500 Subject: [PATCH 15/21] Add project_allocation API test --- tock/api/tests.py | 84 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/tock/api/tests.py b/tock/api/tests.py index 31d4f27b..36a6a144 100644 --- a/tock/api/tests.py +++ b/tock/api/tests.py @@ -180,6 +180,90 @@ def test_get_unsubmitted_timecards(self): ) self.assertEqual(len(queryset), 2) +""" + Adding data to the timecards.json fixture results in failing tests since many tests + assert on the length of a list returned. You can add tests here by creating mock data + inside of setUp() and not worry about breaking existing tests that rely on the timecard + fixture +""" +class FixturelessTimecardsAPITests(WebTest): + def setUp(self): + super(FixturelessTimecardsAPITests, self).setUp() + self.user = UserFactory() + self.userdata = UserData.objects.create(user=self.user) + self.billable_code = AccountingCodeFactory(billable=True) + self.weekly_billed_project = ProjectFactory(accounting_code=self.billable_code,is_weekly_bill=True) + self.period1 = ReportingPeriodFactory(start_date=datetime.datetime(2021, 9, 1)) + self.period2 = ReportingPeriodFactory(start_date=datetime.datetime(2021, 9, 8)) + self.period3 = ReportingPeriodFactory(start_date=datetime.datetime(2021, 9, 14)) + self.period4 = ReportingPeriodFactory(start_date=datetime.datetime(2021, 9, 21)) + self.period5 = ReportingPeriodFactory(start_date=datetime.datetime(2021, 9, 29)) + self.full_allocation_timecard = TimecardFactory(user=self.user, reporting_period=self.period1) + self.three_quarter_allocation_timecard = TimecardFactory(user=self.user, reporting_period=self.period2) + self.half_allocation_timecard = TimecardFactory(user=self.user, reporting_period=self.period3) + self.one_quarter_allocation_timecard = TimecardFactory(user=self.user, reporting_period=self.period4) + self.one_eighth_allocation_timecard = TimecardFactory(user=self.user, reporting_period=self.period5) + self.full_allocation_timecard_objects = [ + TimecardObjectFactory( + timecard=self.full_allocation_timecard, + project=self.weekly_billed_project, + hours_spent=0, + project_allocation=1.000 + ) + ] + self.three_quarter_allocation_timecard_objects = [ + TimecardObjectFactory( + timecard=self.three_quarter_allocation_timecard, + project=self.weekly_billed_project, + hours_spent=0, + project_allocation=0.750 + ) + ] + self.half_allocation_timecard_objects = [ + TimecardObjectFactory( + timecard=self.half_allocation_timecard, + project=self.weekly_billed_project, + hours_spent=0, + project_allocation=0.500 + ) + ] + self.one_quarter_allocation_timecard_objects = [ + TimecardObjectFactory( + timecard=self.one_quarter_allocation_timecard, + project=self.weekly_billed_project, + hours_spent=0, + project_allocation=0.250 + ) + ] + self.one_eighth_allocation_timecard_objects = [ + TimecardObjectFactory( + timecard=self.one_eighth_allocation_timecard, + project=self.weekly_billed_project, + hours_spent=0, + project_allocation=0.125 + ) + ] + + def test_project_allocation_scale_precision(self): + """ + project_allocation allows a scale of 6 digits and a precision of 3 digits + Test to make sure that the API, which relies on TimecardSerializer, follows this convention + """ + all_timecards = client().get( + reverse('TimecardList'), + kwargs={'date': '2021-09-01'}).data + + full_allocation_timecard = all_timecards[0] + three_quarter_allocation_timecard = all_timecards[1] + half_allocation_timecard = all_timecards[2] + one_quarter_allocation_timecard = all_timecards[3] + one_eighth_allocation_timecard = all_timecards[4] + + self.assertEqual(full_allocation_timecard['project_allocation'], "1.000") + self.assertEqual(three_quarter_allocation_timecard['project_allocation'], "0.750") + self.assertEqual(half_allocation_timecard['project_allocation'], "0.500") + self.assertEqual(one_quarter_allocation_timecard['project_allocation'], "0.250") + self.assertEqual(one_eighth_allocation_timecard['project_allocation'], "0.125") class TestAggregates(WebTest): From d57737ec1fb8dc78424d3f9fa47e4f135ef458fc Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Thu, 7 Oct 2021 16:04:25 -0500 Subject: [PATCH 16/21] Fix broken test --- tock/hours/tests/test_integration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tock/hours/tests/test_integration.py b/tock/hours/tests/test_integration.py index e6efc00e..7349edb1 100644 --- a/tock/hours/tests/test_integration.py +++ b/tock/hours/tests/test_integration.py @@ -176,7 +176,7 @@ def test_admin_weekly_bill_timecard_submit(self): res = self.app.get(url, user=self.user) form = res.form form["user"] = self.user.id - form["reporting_period"] = "49" + form["reporting_period"] = self.reporting_period3.id form["timecardobjects-0-project"] = weekly_billed_project.id form["timecardobjects-0-project_allocation"] = "1.0" res = form.submit("submit-timecard").follow() From 69a3545e5ebafbb6f8e4d5e2d84b421ffef8c121 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Thu, 7 Oct 2021 16:08:42 -0500 Subject: [PATCH 17/21] Fix linting --- tock/hours/admin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tock/hours/admin.py b/tock/hours/admin.py index c84cb8f7..d283745f 100644 --- a/tock/hours/admin.py +++ b/tock/hours/admin.py @@ -63,8 +63,8 @@ def optgroups(self, name, value, attrs=None): for subvalue, sublabel in choices: selected = ( # instead of string comparison, use numerical comparison here - any(isclose(float(subvalue), float(v)) for v in value) and - (not has_selected or self.allow_multiple_selected) + any(isclose(float(subvalue), float(v)) for v in value) + and (not has_selected or self.allow_multiple_selected) ) has_selected |= selected subgroup.append(self.create_option( From 075e9ad31eb4bbf00f69a1368d4666c66569db2a Mon Sep 17 00:00:00 2001 From: Caley Woods Date: Thu, 7 Oct 2021 16:15:35 -0500 Subject: [PATCH 18/21] Remove whitespace --- tock/api/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tock/api/tests.py b/tock/api/tests.py index 36a6a144..b4582c32 100644 --- a/tock/api/tests.py +++ b/tock/api/tests.py @@ -243,7 +243,7 @@ def setUp(self): project_allocation=0.125 ) ] - + def test_project_allocation_scale_precision(self): """ project_allocation allows a scale of 6 digits and a precision of 3 digits @@ -252,7 +252,7 @@ def test_project_allocation_scale_precision(self): all_timecards = client().get( reverse('TimecardList'), kwargs={'date': '2021-09-01'}).data - + full_allocation_timecard = all_timecards[0] three_quarter_allocation_timecard = all_timecards[1] half_allocation_timecard = all_timecards[2] From fac7c315f5f6d63206a0477c78933c7c73cb0625 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Tue, 12 Oct 2021 10:39:29 -0500 Subject: [PATCH 19/21] Use our custom decimal choice widget on timecard entry page --- tock/hours/admin.py | 13 +++++++++++- tock/hours/forms.py | 3 ++- tock/hours/tests/test_integration.py | 31 ++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/tock/hours/admin.py b/tock/hours/admin.py index d283745f..caa15a7b 100644 --- a/tock/hours/admin.py +++ b/tock/hours/admin.py @@ -26,6 +26,17 @@ def queryset(self, request, queryset): return queryset +def safe_float(value): + """Convert a string to a float with no exceptions. + + Return NaN if the conversion fails. + """ + try: + return float(value) + except ValueError: + return float("NaN") + + class DecimalChoiceWidget(Select): """A choice widget for decimal typed data. @@ -63,7 +74,7 @@ def optgroups(self, name, value, attrs=None): for subvalue, sublabel in choices: selected = ( # instead of string comparison, use numerical comparison here - any(isclose(float(subvalue), float(v)) for v in value) + any(isclose(safe_float(subvalue), safe_float(v)) for v in value) and (not has_selected or self.allow_multiple_selected) ) has_selected |= selected diff --git a/tock/hours/forms.py b/tock/hours/forms.py index 9905cb12..5f24f60c 100644 --- a/tock/hours/forms.py +++ b/tock/hours/forms.py @@ -11,6 +11,7 @@ from projects.models import AccountingCode, Project from .models import ReportingPeriod, Timecard, TimecardObject +from .admin import DecimalChoiceWidget class ReportingPeriodForm(forms.ModelForm): @@ -143,7 +144,7 @@ class TimecardObjectForm(forms.ModelForm): project_allocation = forms.ChoiceField( choices=settings.PROJECT_ALLOCATION_CHOICES, required=False, - widget=forms.Select(attrs={'onchange' : "populateHourTotals();"}) + widget=DecimalChoiceWidget(attrs={'onchange' : "populateHourTotals();"}) ) hours_spent = forms.DecimalField( min_value=0, diff --git a/tock/hours/tests/test_integration.py b/tock/hours/tests/test_integration.py index 7349edb1..d29e0423 100644 --- a/tock/hours/tests/test_integration.py +++ b/tock/hours/tests/test_integration.py @@ -3,6 +3,7 @@ from django.urls import reverse from django_webtest import WebTest +from webtest.forms import Hidden from test_common import ProtectedViewTestCase @@ -161,6 +162,36 @@ def test_timecard_submit_twice(self): # successful POST will give a 302 redirect self.assertEqual(res.status_code, 302) + def test_timecard_save_weekly_bill(self): + # Make a new timecard so we can save it + hours.models.Timecard.objects.create( + user=self.user, + reporting_period=self.reporting_period3, + ) + date = self.reporting_period3.start_date.strftime('%Y-%m-%d') + url = reverse('reportingperiod:UpdateTimesheet', + kwargs={'reporting_period': date},) + res = self.app.get(url, user=self.user) + form = res.form # only one form on the page + + weekly_billed_project = projects.models.Project.objects.get(name='Weekly Billing') + form["timecardobjects-0-project"] = weekly_billed_project.id + form["timecardobjects-0-hours_spent"] = 0 + form["timecardobjects-0-project_allocation"] = "0.5" + + # normally added by JS in a browser, we add the save_only field manually here + # technique from https://stackoverflow.com/a/23877996 + field = Hidden(form, "input", None, 999, "1") + form.fields["save_only"] = [field] + form.field_order.append(("save_only", field)) + + page = form.submit().follow() # follow the 302 redirect on success + + # The project allocation is properly displayed + self.assertEqual(page.form["timecardobjects-0-project_allocation"].value, "0.5") + + + class TestAdmin(ProtectedViewTestCase, WebTest): fixtures = [ From 3029990fd6487c01ffa43c0bb29766e4a0705ad1 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Wed, 13 Oct 2021 13:38:01 -0500 Subject: [PATCH 20/21] Remove additional help banner from timecard page --- tock/tock/templates/hours/timecard_form.html | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/tock/tock/templates/hours/timecard_form.html b/tock/tock/templates/hours/timecard_form.html index 598a69d9..45fcf584 100644 --- a/tock/tock/templates/hours/timecard_form.html +++ b/tock/tock/templates/hours/timecard_form.html @@ -62,15 +62,6 @@

{{ timecard_note.title }}

{% endfor %} -
-
-

- For additional help and guidance with using Tock and entering hours, please consult the TTS Handbook page on Tock. -

-
-
-
{% csrf_token %} @@ -181,4 +172,4 @@

{{ timecard_note.title }}

let objectId = {{ object.id }}; -{% endblock %} \ No newline at end of file +{% endblock %} From 5808694c530c0fe539c5bd893b8d08275c1028b4 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Thu, 11 Nov 2021 08:06:31 +0000 Subject: [PATCH 21/21] fix: package.json & package-lock.json to reduce vulnerabilities The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JS-USWDS-1656800 --- package-lock.json | 218 +++++----------------------------------------- package.json | 2 +- 2 files changed, 25 insertions(+), 195 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8a85eaf1..c581ee3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -715,29 +715,6 @@ "chalk": "^4.0.0" } }, - "@nodelib/fs.scandir": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", - "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", - "requires": { - "@nodelib/fs.stat": "2.0.4", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", - "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==" - }, - "@nodelib/fs.walk": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", - "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", - "requires": { - "@nodelib/fs.scandir": "2.1.4", - "fastq": "^1.6.0" - } - }, "@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -914,22 +891,6 @@ "es6-promisify": "^5.0.0" } }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "dependencies": { - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" - } - } - }, "ansi-escapes": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", @@ -992,11 +953,6 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" - }, "async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", @@ -1083,7 +1039,8 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, "binary-extensions": { "version": "2.2.0", @@ -1094,6 +1051,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1224,11 +1182,6 @@ "resolved": "https://registry.npmjs.org/classlist-polyfill/-/classlist-polyfill-1.2.0.tgz", "integrity": "sha1-k1vC39lFiodrJ5YXUUY4vKqWSi4=" }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" - }, "cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", @@ -1319,7 +1272,8 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "concat-stream": { "version": "1.6.2", @@ -1447,31 +1401,6 @@ "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", "dev": true }, - "del": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", - "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", - "requires": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - }, - "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "requires": { - "glob": "^7.1.3" - } - } - } - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1490,21 +1419,6 @@ "integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==", "dev": true }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "requires": { - "path-type": "^4.0.0" - }, - "dependencies": { - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" - } - } - }, "domexception": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", @@ -1533,11 +1447,6 @@ "integrity": "sha512-JTPxdUibkefeomWNaYs8lI/x/Zb4cOhZWX+d7kpzsNKzUd07pNuo/AcHeNJ/qgEchxM1IAxda9aaGUhKN/poOg==", "dev": true }, - "elem-dataset": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/elem-dataset/-/elem-dataset-2.0.0.tgz", - "integrity": "sha512-e7gieGopWw5dMdEgythH3lUS7nMizutPDTtkzfQW/q2gCvFnACyNnK3ytCncAXKxdBXQWcXeKaYTTODiMnp8mw==" - }, "element-closest": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/element-closest/-/element-closest-2.0.2.tgz", @@ -1707,19 +1616,6 @@ "yauzl": "^2.10.0" } }, - "fast-glob": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", - "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", - "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" - } - }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -1732,14 +1628,6 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, - "fastq": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.1.tgz", - "integrity": "sha512-AWuv6Ery3pM+dY7LYS8YIaCiQvUaos9OB1RyNgaOWnaX+Tik7Onvcsf8x8c+YtDeT0maYLniBip2hox5KtEXXA==", - "requires": { - "reusify": "^1.0.4" - } - }, "fb-watchman": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", @@ -1916,7 +1804,8 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "fsevents": { "version": "2.3.2", @@ -1958,6 +1847,7 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -2019,23 +1909,11 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, - "globby": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz", - "integrity": "sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==", - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - } - }, "graceful-fs": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.5.tgz", - "integrity": "sha512-kBBSQbz2K0Nyn+31j/w36fUfxkBW9/gfwRWdUY1ULReH3iokVJgddZAFcD1D0xlgTmFxJCbUkUclAlc6/IDJkw==" + "integrity": "sha512-kBBSQbz2K0Nyn+31j/w36fUfxkBW9/gfwRWdUY1ULReH3iokVJgddZAFcD1D0xlgTmFxJCbUkUclAlc6/IDJkw==", + "dev": true }, "growly": { "version": "1.3.0", @@ -2161,11 +2039,6 @@ "safer-buffer": ">= 2.1.2 < 3" } }, - "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" - }, "import-local": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", @@ -2186,6 +2059,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -2194,7 +2068,8 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "ini": { "version": "1.3.8", @@ -2378,16 +2253,6 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, - "is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==" - }, - "is-path-inside": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", - "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==" - }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -3348,11 +3213,6 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" - }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -3402,20 +3262,6 @@ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, "mime": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.0.tgz", @@ -3447,6 +3293,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3589,6 +3436,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1" } @@ -3660,14 +3508,6 @@ "p-limit": "^2.2.0" } }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "requires": { - "aggregate-error": "^3.0.0" - } - }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -3695,7 +3535,8 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true }, "path-key": { "version": "3.1.1", @@ -3942,11 +3783,6 @@ "signal-exit": "^3.0.2" } }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" - }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -3962,11 +3798,6 @@ "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", "dev": true }, - "run-parallel": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", - "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==" - }, "rxjs": { "version": "6.6.3", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", @@ -4076,7 +3907,8 @@ "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true }, "source-map": { "version": "0.6.1", @@ -4369,15 +4201,12 @@ "dev": true }, "uswds": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/uswds/-/uswds-2.10.0.tgz", - "integrity": "sha512-A6HjL42ryf9pdfbm6JrGxZywKzCZraHO4v3Ve21uFDqoA3ijoNjiSYME+3SG86CIgC6zRasrbQVuI3bvd3Xv2w==", + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/uswds/-/uswds-2.12.2.tgz", + "integrity": "sha512-Y9YeILoe4xiH26JpyznSbIsC8osHY88QjTFM3K5r7GcSuryauu7CpPnQdBUCw5R7myc24N5X8S/nxnXvOEtudg==", "requires": { "classlist-polyfill": "^1.0.3", - "del": "^6.0.0", "domready": "^1.0.8", - "elem-dataset": "^2.0.0", - "lodash.debounce": "^4.0.7", "object-assign": "^4.1.1", "receptor": "^1.0.0", "resolve-id-refs": "^0.1.0" @@ -4559,7 +4388,8 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "write-file-atomic": { "version": "3.0.3", diff --git a/package.json b/package.json index 255a1f86..d43b5bb3 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "chosen-js": "^1.8.7", "jquery": "^3.6.0", "sass": "^1.35.1", - "uswds": "^2.10.0" + "uswds": "^2.12.2" }, "devDependencies": { "jest": "^27.0.6",