diff --git a/redcap/methods/surveys.py b/redcap/methods/surveys.py index fc2b785..0597e07 100644 --- a/redcap/methods/surveys.py +++ b/redcap/methods/surveys.py @@ -13,11 +13,11 @@ class Surveys(Base): def export_survey_link( self, - instrument: str, record: str, + instrument: str, event: Optional[str] = None, repeat_instance: int = 1, - ): + ) -> str: """ Export one survey link @@ -25,44 +25,174 @@ def export_survey_link( The passed instrument must be set up as a survey instrument. Args: + record: + Name of the record instrument: Name of instrument as seen in the Data Dictionary (metadata). + event: + Unique event name, only used in longitudinal projects + repeat_instance: + only for projects with repeating instruments/events) + The repeat instance number of the repeating event (if longitudinal) + or the repeating instrument (if classic or longitudinal). + Default value is '1'. + + Returns: + URL of survey link requested + + Examples: + >>> proj.export_survey_link(record="1", instrument="form_1", event="event_1_arm_1") + 'https://redcapdemo.vumc.org/surveys/?s=...' + """ + payload = self._initialize_payload( + content="surveyLink", + # Hard-coded due to the nature of the response + return_format_type="csv", + ) + + payload["record"] = record + payload["instrument"] = instrument + payload["repeat_instance"] = repeat_instance + + if event: + payload["event"] = event + + return cast(str, self._call_api(payload, return_type="str")) + + def export_survey_queue_link( + self, + record: str, + ) -> str: + """ + Export one survey queue link + + Note: + The passed instrument must be set up as a survey instrument. The + survey queue must be enabled for the project. + + Args: + record: + Name of the record + + Returns: + URL of survey queue link requested + + Examples: + >>> proj.export_survey_queue_link(record="1") + 'https://redcapdemo.vumc.org/surveys/?sq=...' + """ + payload = self._initialize_payload( + content="surveyQueueLink", + # Hard-coded due to the nature of the response + return_format_type="csv", + ) + + payload["record"] = record + + return cast(str, self._call_api(payload, return_type="str")) + + def export_survey_access_code( + self, + record: str, + instrument: str, + event: Optional[str] = None, + repeat_instance: int = 1, + ) -> str: + # pylint: disable=line-too-long + """ + Export a Survey Access Code for a Participant + + Note: + The passed instrument must be set up as a survey instrument. + + Args: record: Name of the record + instrument: + Name of instrument as seen in the Data Dictionary (metadata). event: Unique event name, only used in longitudinal projects repeat_instance: - only for projects with repeating instruments/events) - The repeat instance number of the repeating event (if longitudinal) - or the repeating instrument (if classic or longitudinal). + only for projects with repeating instruments/events) + The repeat instance number of the repeating event (if longitudinal) + or the repeating instrument (if classic or longitudinal). Default value is '1'. Returns: - Str: - URL of survey link requested + A survey access code for a specified record and data collection + instrument Examples: - >>> proj.export_survey_link(instrument="form_1", record="5", event="event_1_arm_1") - https://redcap.mytld.com/surveys/?s=6B2zFAEWPVSrXnnx - """ + >>> proj.export_survey_access_code(record="1", instrument="form_1", event="event_1_arm_1") + '...' + """ + # pylint: enable=line-too-long payload = self._initialize_payload( - content="surveyLink" + content="surveyAccessCode", + # Hard-coded due to the nature of the response + return_format_type="csv", ) - payload["instrument"] = instrument + payload["record"] = record + payload["instrument"] = instrument + payload["repeat_instance"] = repeat_instance + if event: payload["event"] = event - payload["repeat_instance"] = repeat_instance - return_type = "str" - response = cast(Union[Json, str], self._call_api(payload, return_type)) + return cast(str, self._call_api(payload, return_type="str")) - return self._return_data( - response=response, - content="surveyLink", - format_type=return_type, + def export_survey_return_code( + self, + record: str, + instrument: str, + event: Optional[str] = None, + repeat_instance: int = 1, + ) -> str: + # pylint: disable=line-too-long + """ + Export a Survey Return Code for a Participant + + Note: + The passed instrument must be set up as a survey instrument, which has return codes enabled. + + Args: + record: + Name of the record + instrument: + Name of instrument as seen in the Data Dictionary (metadata). + event: + Unique event name, only used in longitudinal projects + repeat_instance: + only for projects with repeating instruments/events) + The repeat instance number of the repeating event (if longitudinal) + or the repeating instrument (if classic or longitudinal). + Default value is '1'. + + Returns: + A survey return code for a specified record and data collection + instrument + + Examples: + >>> proj.export_survey_return_code(record="1", instrument="form_1", event="event_1_arm_1") + '...' + """ + # pylint: enable=line-too-long + payload = self._initialize_payload( + content="surveyReturnCode", + # Hard-coded due to the nature of the response + return_format_type="csv", ) + payload["record"] = record + payload["instrument"] = instrument + payload["repeat_instance"] = repeat_instance + + if event: + payload["event"] = event + + return cast(str, self._call_api(payload, return_type="str")) + def export_survey_participant_list( self, instrument: str, @@ -103,7 +233,8 @@ def export_survey_participant_list( 'survey_access_code': ...}] """ payload = self._initialize_payload( - content="participantList", format_type=format_type + content="participantList", + format_type=format_type, ) payload["instrument"] = instrument if event: diff --git a/tests/data/doctest_project.xml b/tests/data/doctest_project.xml index 2de7f3e..495236a 100644 --- a/tests/data/doctest_project.xml +++ b/tests/data/doctest_project.xml @@ -1,27 +1,43 @@ - - + + - PyCap doctest_project - This file contains the metadata, events, and data for REDCap project "PyCap doctest_project". - PyCap doctest_project + doctest project + This file contains the metadata, events, and data for REDCap project "doctest project". + doctest project 1 + 1 + 1 0 1 + 0 + 1 + 0 + 0 + 1 + + 2 + 0 0 0 - 0 - 0 - 0 - 0 - 1 - 0 + 0 + + + + + + + + 1 + + + 0 @@ -36,24 +52,24 @@ +<p>Have a nice day!</p>" stop_action_acknowledgement="" stop_action_delete_response="0" question_by_section="0" display_page_number="0" question_auto_numbering="1" survey_enabled="1" save_and_return="1" save_and_return_code_bypass="0" logo="" hide_title="0" view_results="0" min_responses_view_results="10" check_diversity_view_results="0" end_survey_redirect_url="" survey_expiration="" promis_skip_question="0" survey_auth_enabled_single="0" edit_completed_response="0" hide_back_button="0" show_required_field_text="1" confirmation_email_subject="" confirmation_email_content="" confirmation_email_from="" confirmation_email_from_display="" confirmation_email_attach_pdf="0" confirmation_email_attachment="" text_to_speech="0" text_to_speech_language="en" end_survey_redirect_next_survey="0" end_survey_redirect_next_survey_logic="" theme="" text_size="1" font_family="16" theme_text_buttons="" theme_bg_page="" theme_text_title="" theme_bg_title="" theme_text_sectionheader="" theme_bg_sectionheader="" theme_text_question="" theme_bg_question="" enhanced_choices="0" repeat_survey_enabled="0" repeat_survey_btn_text="" repeat_survey_btn_location="HIDDEN" response_limit="" response_limit_include_partials="1" response_limit_custom_text="<p>Thank you for your interest; however, the survey is closed because the maximum number of responses has been reached.</p>" survey_time_limit_days="" survey_time_limit_hours="" survey_time_limit_minutes="" email_participant_field="" end_of_survey_pdf_download="0" pdf_save_to_field="" pdf_save_to_event_id="" pdf_save_translated="0" pdf_auto_archive="0" pdf_econsent_version="" pdf_econsent_type="" pdf_econsent_firstname_field="" pdf_econsent_firstname_event_id="" pdf_econsent_lastname_field="" pdf_econsent_lastname_event_id="" pdf_econsent_dob_field="" pdf_econsent_dob_event_id="" pdf_econsent_allow_edit="1" pdf_econsent_signature_field1="" pdf_econsent_signature_field2="" pdf_econsent_signature_field3="" pdf_econsent_signature_field4="" pdf_econsent_signature_field5="" survey_width_percent="" survey_show_font_resize="1" survey_btn_text_prev_page="" survey_btn_text_next_page="" survey_btn_text_submit="" survey_btn_hide_submit="0" survey_btn_hide_submit_logic=""/> + + + - + - + - - - - + - + @@ -132,7 +148,7 @@ - + diff --git a/tests/data/test_long_project.xml b/tests/data/test_long_project.xml index 3f1b778..45fb5d7 100644 --- a/tests/data/test_long_project.xml +++ b/tests/data/test_long_project.xml @@ -1,27 +1,43 @@ - - + + - PyCap test_long_project: 01-19 18:35:09 - This file contains the metadata, events, and data for REDCap project "PyCap test_long_project: 01-19 18:35:09". - PyCap test_long_project: 01-19 18:35:09 + New Test Long Project + This file contains the metadata, events, and data for REDCap project "New Test Long Project". + New Test Long Project 0 + 1 + 1 0 1 + 0 + 1 + 0 + 0 + 1 + + 2 + 0 0 0 - 0 - 0 - 0 - 0 - 1 - 0 + 0 + + + + + + + + 1 + + + 0 @@ -35,13 +51,14 @@ +<p>Have a nice day!</p>" stop_action_acknowledgement="" stop_action_delete_response="0" question_by_section="0" display_page_number="0" question_auto_numbering="1" survey_enabled="1" save_and_return="1" save_and_return_code_bypass="0" logo="" hide_title="0" view_results="0" min_responses_view_results="10" check_diversity_view_results="0" end_survey_redirect_url="" survey_expiration="" promis_skip_question="0" survey_auth_enabled_single="0" edit_completed_response="0" hide_back_button="0" show_required_field_text="1" confirmation_email_subject="" confirmation_email_content="" confirmation_email_from="" confirmation_email_from_display="" confirmation_email_attach_pdf="0" confirmation_email_attachment="" text_to_speech="0" text_to_speech_language="en" end_survey_redirect_next_survey="0" end_survey_redirect_next_survey_logic="" theme="" text_size="1" font_family="16" theme_text_buttons="" theme_bg_page="" theme_text_title="" theme_bg_title="" theme_text_sectionheader="" theme_bg_sectionheader="" theme_text_question="" theme_bg_question="" enhanced_choices="0" repeat_survey_enabled="0" repeat_survey_btn_text="" repeat_survey_btn_location="HIDDEN" response_limit="" response_limit_include_partials="1" response_limit_custom_text="<p>Thank you for your interest; however, the survey is closed because the maximum number of responses has been reached.</p>" survey_time_limit_days="" survey_time_limit_hours="" survey_time_limit_minutes="" email_participant_field="" end_of_survey_pdf_download="0" pdf_save_to_field="" pdf_save_to_event_id="" pdf_save_translated="0" pdf_auto_archive="0" pdf_econsent_version="" pdf_econsent_type="" pdf_econsent_firstname_field="" pdf_econsent_firstname_event_id="" pdf_econsent_lastname_field="" pdf_econsent_lastname_event_id="" pdf_econsent_dob_field="" pdf_econsent_dob_event_id="" pdf_econsent_allow_edit="1" pdf_econsent_signature_field1="" pdf_econsent_signature_field2="" pdf_econsent_signature_field3="" pdf_econsent_signature_field4="" pdf_econsent_signature_field5="" survey_width_percent="" survey_show_font_resize="1" survey_btn_text_prev_page="" survey_btn_text_next_page="" survey_btn_text_submit="" survey_btn_hide_submit="0" survey_btn_hide_submit_logic=""/> - - - + + + + - + @@ -1163,7 +1180,7 @@ - + diff --git a/tests/integration/test_long_project.py b/tests/integration/test_long_project.py index a506674..cbff629 100644 --- a/tests/integration/test_long_project.py +++ b/tests/integration/test_long_project.py @@ -17,6 +17,36 @@ def test_is_longitudinal(long_project): assert long_project.is_longitudinal +@pytest.mark.integration +def test_export_survey_link(long_project): + link = long_project.export_survey_link( + instrument="contact_info", event="enrollment_arm_1", record="1" + ) + assert link.startswith("https://redcapdemo.vumc.org/surveys/?s=") + + +@pytest.mark.integration +def test_export_survey_queue_link(long_project): + link = long_project.export_survey_queue_link(record="1") + assert link.startswith("https://redcapdemo.vumc.org/surveys/?sq=") + + +@pytest.mark.integration +def test_export_survey_access_code(long_project): + code = long_project.export_survey_access_code( + record="1", instrument="contact_info", event="enrollment_arm_1" + ) + assert len(code) == 9 + + +@pytest.mark.integration +def test_export_survey_return_code(long_project): + code = long_project.export_survey_return_code( + record="1", instrument="contact_info", event="enrollment_arm_1" + ) + assert len(code) == 8 + + @pytest.mark.integration def test_survey_participant_export(long_project): data = long_project.export_survey_participant_list( diff --git a/tests/unit/callback_utils.py b/tests/unit/callback_utils.py index 8ab167e..3d4249d 100644 --- a/tests/unit/callback_utils.py +++ b/tests/unit/callback_utils.py @@ -781,6 +781,66 @@ def handle_long_project_survey_participants_request(**kwargs) -> Any: return (201, headers, json.dumps(resp)) +def handle_long_project_survey_link_request(**kwargs) -> Any: + """Get the survey link for a record and instrument and event""" + data = kwargs["data"] + headers = kwargs["headers"] + resp = None + + if ( + "test" in data.get("instrument") + and "raw" in data.get("event") + and "1" in data.get("record") + ): + resp = "https://redcapdemo.vumc.org/surveys/?s=DMgheEPAgLETxJkf" + + return (201, headers, resp) + + +def handle_long_project_survey_queue_link_request(**kwargs) -> Any: + """Get the survey queue link for a record""" + data = kwargs["data"] + headers = kwargs["headers"] + resp = None + + if "1" in data.get("record"): + resp = "https://redcapdemo.vumc.org/surveys/?sq=DMgheEPAgLETxJkf" + + return (201, headers, resp) + + +def handle_long_project_survey_access_code_request(**kwargs) -> Any: + """Get the survey access code for a record and instrument and event""" + data = kwargs["data"] + headers = kwargs["headers"] + resp = None + + if ( + "test" in data.get("instrument") + and "raw" in data.get("event") + and "1" in data.get("record") + ): + resp = "AAAAAAAAA" + + return (201, headers, resp) + + +def handle_long_project_survey_return_code_request(**kwargs) -> Any: + """Get the survey return code for a record and instrument and event""" + data = kwargs["data"] + headers = kwargs["headers"] + resp = None + + if ( + "test" in data.get("instrument") + and "raw" in data.get("event") + and "1" in data.get("record") + ): + resp = "AAAAAAAA" + + return (201, headers, resp) + + def get_simple_project_request_handler(request_type: str) -> Callable: """Given a request type, extract the handler function""" handlers_dict = { @@ -823,6 +883,10 @@ def get_long_project_request_handler(request_type: str) -> Callable: "pdf": handle_long_project_pdf_request, "record": handle_long_project_records_request, "report": handle_long_project_reports_request, + "surveyLink": handle_long_project_survey_link_request, + "surveyQueueLink": handle_long_project_survey_queue_link_request, + "surveyAccessCode": handle_long_project_survey_access_code_request, + "surveyReturnCode": handle_long_project_survey_return_code_request, "version": handle_long_project_version_request, } diff --git a/tests/unit/test_long_project.py b/tests/unit/test_long_project.py index 9c4a147..656deeb 100644 --- a/tests/unit/test_long_project.py +++ b/tests/unit/test_long_project.py @@ -71,6 +71,30 @@ def test_export_survey_participants_list(long_project): assert is_json(res) +def test_export_survey_link(long_project): + res = long_project.export_survey_link(instrument="test", record="1", event="raw") + assert res.startswith("https://redcapdemo.vumc.org/surveys/?s=") + + +def test_export_survey_queue_link(long_project): + res = long_project.export_survey_queue_link(record="1") + assert res.startswith("https://redcapdemo.vumc.org/surveys/?sq=") + + +def test_export_survey_access_code(long_project): + res = long_project.export_survey_access_code( + instrument="test", record="1", event="raw" + ) + assert len(res) == 9 + + +def test_export_survey_return_code(long_project): + res = long_project.export_survey_return_code( + instrument="test", record="1", event="raw" + ) + assert len(res) == 8 + + def test_metadata_import_handles_api_error(long_project): metadata = long_project.export_metadata()