diff --git a/coldfront/core/allocation/management/commands/data/brc_allocation_periods.json b/coldfront/core/allocation/management/commands/data/brc_allocation_periods.json index ad4af549f..0a0d042e5 100644 --- a/coldfront/core/allocation/management/commands/data/brc_allocation_periods.json +++ b/coldfront/core/allocation/management/commands/data/brc_allocation_periods.json @@ -24,6 +24,11 @@ "start_date": "2024-06-01", "end_date": "2025-05-31" }, + { + "name": "Allowance Year 2025 - 2026", + "start_date": "2025-06-01", + "end_date": "2026-05-31" + }, { "name": "Fall Semester 2021", "start_date": "2021-08-18", @@ -143,5 +148,45 @@ "name": "Summer Sessions 2024 - Session F", "start_date": "2024-07-01", "end_date": "2024-07-19" + }, + { + "name": "Fall Semester 2024", + "start_date": "2024-08-21", + "end_date": "2024-12-20" + }, + { + "name": "Spring Semester 2025", + "start_date": "2025-01-14", + "end_date": "2025-05-16" + }, + { + "name": "Summer Sessions 2025 - Session A", + "start_date": "2025-05-27", + "end_date": "2025-07-03" + }, + { + "name": "Summer Sessions 2025 - Session B", + "start_date": "2025-06-09", + "end_date": "2025-08-15" + }, + { + "name": "Summer Sessions 2025 - Session C", + "start_date": "2025-06-23", + "end_date": "2025-08-15" + }, + { + "name": "Summer Sessions 2025 - Session D", + "start_date": "2025-07-07", + "end_date": "2025-08-15" + }, + { + "name": "Summer Sessions 2025 - Session E", + "start_date": "2025-07-28", + "end_date": "2025-08-15" + }, + { + "name": "Summer Sessions 2025 - Session F", + "start_date": "2025-07-07", + "end_date": "2025-07-25" } ] \ No newline at end of file diff --git a/coldfront/core/allocation/management/commands/data/lrc_allocation_periods.json b/coldfront/core/allocation/management/commands/data/lrc_allocation_periods.json index 00c1609b9..7f9d8b034 100644 --- a/coldfront/core/allocation/management/commands/data/lrc_allocation_periods.json +++ b/coldfront/core/allocation/management/commands/data/lrc_allocation_periods.json @@ -18,5 +18,10 @@ "name": "Allowance Year 2024 - 2025", "start_date": "2024-10-01", "end_date": "2025-09-30" + }, + { + "name": "Allowance Year 2025 - 2026", + "start_date": "2025-10-01", + "end_date": "2026-09-30" } ] \ No newline at end of file diff --git a/coldfront/core/utils/tests/test_export_data.py b/coldfront/core/utils/tests/test_export_data.py index 0e74dd72c..8df3cd057 100644 --- a/coldfront/core/utils/tests/test_export_data.py +++ b/coldfront/core/utils/tests/test_export_data.py @@ -8,11 +8,15 @@ from django.contrib.auth.models import User from django.core.management import call_command, CommandError +from django.urls import reverse +from django import forms +from http import HTTPStatus from coldfront.api.statistics.utils import get_accounting_allocation_objects, \ create_project_allocation, create_user_project_allocation from coldfront.core.allocation.models import AllocationAttributeType, \ - AllocationUserAttribute + AllocationUserAttribute, AllocationRenewalRequest, AllocationPeriod, \ + AllocationRenewalRequestStatusChoice from coldfront.core.statistics.models import Job from coldfront.core.user.models import UserProfile from coldfront.core.utils.common import display_time_zone_date_to_utc_datetime @@ -23,6 +27,8 @@ ProjectUser, ProjectUserStatusChoice, ProjectUserRoleChoice, \ ProjectAllocationRequestStatusChoice, SavioProjectAllocationRequest, \ VectorProjectAllocationRequest +from coldfront.core.project.forms_.renewal_forms.request_forms import ProjectRenewalSurveyForm +from coldfront.core.project.utils_.renewal_utils import get_current_allowance_year_period from coldfront.core.resource.models import Resource from coldfront.core.resource.utils_.allowance_utils.constants import BRCAllowances from coldfront.core.resource.utils_.allowance_utils.interface import ComputingAllowanceInterface @@ -999,3 +1005,194 @@ def test_get_new_project_survey_responses_allowance_type(self): err.seek(0) self.assertEqual(err.read(), '') + +class TestRenewalSurveyResponses(TestBase): + """ Test class to test export data subcommand renewal_survey_responses + runs correctly """ + + @enable_deployment('BRC') + def setUp(self): + super().setUp() + + fixtures = [] + filtered_fixtures = [] + + allocation_period = AllocationPeriod.objects.get( + name__exact='Allowance Year 2024 - 2025') + + for index in range(5): + + renewal_survey = { + 'brc_feedback': 'N/A', + 'mybrc_comments': '', + 'do_you_use_mybrc': 'no', + 'classes_being_taught': f'sample answer {index}', + 'colleague_suggestions': 'N/A', + 'grants_supported_by_brc': 'Grant #1 etc. etc.\r\nGrant #2 etc. etc.', + 'which_brc_services_used': ['savio_hpc', 'srdc'], + 'indicate_topic_interests': ['have_visited_rdmp_website', + 'have_had_rdmp_event_or_consultation', + 'want_to_learn_security_and_have_rdm_consult'], + 'brc_recommendation_rating': '6', + 'publications_supported_by_brc': 'N/A', + 'which_open_ondemand_apps_used': ['desktop', 'matlab', + 'jupyter_notebook', 'vscode_server'], + 'recruitment_or_retention_cases': 'N/A', + 'brc_recommendation_rating_reason': 'Easy to use', + 'how_important_to_research_is_brc': '5', + 'training_session_other_topics_of_interest': 'None', + 'how_brc_helped_bootstrap_computational_methods': 'N/A', + 'training_session_usefulness_of_basic_savio_cluster': '4', + 'training_session_usefulness_of_singularity_on_savio': '4', + 'training_session_usefulness_of_advanced_savio_cluster': '2', + 'training_session_usefulness_of_analytic_envs_on_demand': '5', + 'training_session_usefulness_of_computational_platforms_training': '3'} + + active_project_status = ProjectStatusChoice.objects.get(name='Active') + + project_prefix = 'fc_' if index % 2 else 'pc_' + project = Project.objects.create( + name=f'{project_prefix}test_project{index}', + status=active_project_status) + + requester = User.objects.create( + email=f'requester{index}@email.com', + first_name='Requester', + last_name=f'User {index}', + username=f'requester{index}') + pi = User.objects.create( + email=f'pi{index}@email.com', + first_name='PI', + last_name=f'User {index}', + username=f'pi{index}') + allocation_period_start_utc = display_time_zone_date_to_utc_datetime( + allocation_period.start_date) + fixture = AllocationRenewalRequest.objects.create( + requester=requester, + pi=pi, + computing_allowance=Resource.objects.get( + name=BRCAllowances.FCA), + allocation_period=allocation_period, + status=AllocationRenewalRequestStatusChoice.objects.get( + name='Under Review'), + renewal_survey_answers = renewal_survey, + pre_project=project, + post_project=project, + request_time=allocation_period_start_utc - datetime.timedelta(days=1)) + + fixtures.append(fixture) + if index % 2: + filtered_fixtures.append(fixture) + + fixtures = list(sorted( + fixtures, key=lambda x: x.pre_project.name, reverse=True)) + filtered_fixtures = list(sorted( + filtered_fixtures, key=lambda x: x.pre_project.name, reverse=True)) + self.fixtures = fixtures + self.filtered_fixtures = filtered_fixtures + + @enable_deployment('BRC') + def test_get_renewal_survey_responses_json(self): + out, err = StringIO(''), StringIO('') + call_command('export_data', 'renewal_survey_responses', + '--format=json', + '--allocation_period=Allowance Year 2024 - 2025', + stdout=out, stderr=err) + sys.stdout = sys.__stdout__ + + out.seek(0) + output = json.loads(''.join(out.readlines())) + for index, item in enumerate(output): + project_name = item.pop('project_name') + fixture = self.fixtures[index] + self.assertEqual(project_name, fixture.pre_project.name) + self.assertEqual(item['renewal_survey_responses'], + self.swap_form_answer_id_for_text( + fixture.renewal_survey_answers)) + err.seek(0) + self.assertEqual(err.read(), '') + + @enable_deployment('BRC') + def test_get_renewal_survey_responses_csv(self): + out, err = StringIO(''), StringIO('') + call_command('export_data', 'renewal_survey_responses', + '--format=csv', + '--allocation_period=Allowance Year 2024 - 2025', + stdout=out, stderr=err) + sys.stdout = sys.__stdout__ + + out.seek(0) + reader = DictReader(out.readlines()) + for index, item in enumerate(reader): + project_name = item.pop('project_name') + project_title = item.pop('project_title') + project_pi = item.pop('project_pi') + fixture = self.fixtures[index] + self.assertEqual(project_title, fixture.pre_project.title) + self.assertEqual(project_pi, fixture.pi.username) + self.assertEqual(project_name, fixture.pre_project.name) + self.assertEqual(len(item), len(fixture.renewal_survey_answers)) + sample = ('6. Based upon your overall experience using BRC ' + 'services, how likely are you to recommend Berkeley ' + 'Research Computing to others?') + sample_answer = '6' + self.assertEqual(item[sample], sample_answer) + + err.seek(0) + self.assertEqual(err.read(), '') + + @enable_deployment('BRC') + def test_get_renewal_survey_responses_allowance_type(self): + out, err = StringIO(''), StringIO('') + call_command('export_data', 'renewal_survey_responses', + '--format=json', + '--allocation_period=Allowance Year 2024 - 2025', + '--allowance_type=fc_', stdout=out, stderr=err) + sys.stdout = sys.__stdout__ + + out.seek(0) + output = json.loads(''.join(out.readlines())) + for index, item in enumerate(output): + project_name = item.pop('project_name') + fixture = self.filtered_fixtures[index] + self.assertEqual(project_name, fixture.pre_project.name) + self.assertEqual(item['renewal_survey_responses'], + self.swap_form_answer_id_for_text( + fixture.renewal_survey_answers)) + + err.seek(0) + self.assertEqual(err.read(), '') + + @staticmethod + def swap_form_answer_id_for_text(survey): + ''' + Takes a survey, a dict mapping survey question IDs to answer IDs. + Uses ProjectRenewalSurveyForm. + Swaps answer IDs for answer text, then question IDs for question text. + Returns the modified survey. + + Parameter + ---------- + survey : survey to modify + ''' + multiple_choice_fields = {} + form = ProjectRenewalSurveyForm() + for k, v in form.fields.items(): + # Only ChoiceField or MultipleChoiceField + # (in this specific survey form) have choices + if (isinstance(v, (forms.MultipleChoiceField, forms.ChoiceField))): + multiple_choice_fields[k] = { + _k: _v for _k, _v in form.fields[k].choices} + for question, answer in survey.items(): + if question in multiple_choice_fields.keys(): + sub_map = multiple_choice_fields[question] + if (isinstance(answer, list)): + # Multiple choice, array + survey[question] = [sub_map.get(i,i) for i in answer] + elif answer != '': + # Single choice replacement + survey[question] = sub_map[answer] + # Change keys of survey (question IDs) to be the human-readable text + for id, text in form.fields.items(): + survey[text.label] = survey.pop(id) + return survey \ No newline at end of file