From f1c7d8cb0dac2749338bfc2338179fee8798ca09 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Fri, 20 Dec 2024 02:40:27 -0800 Subject: [PATCH 01/30] add init --- parsons/__init__.py | 1 + parsons/newmode/__init__.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/parsons/__init__.py b/parsons/__init__.py index 227aac8fea..9f034230a4 100644 --- a/parsons/__init__.py +++ b/parsons/__init__.py @@ -73,6 +73,7 @@ ("parsons.mobilize_america.ma", "MobilizeAmerica"), ("parsons.nation_builder.nation_builder", "NationBuilder"), ("parsons.newmode.newmode", "Newmode"), + ("parsons.newmode.newmode_v2", "NewmodeV2"), ("parsons.ngpvan.van", "VAN"), ("parsons.notifications.gmail", "Gmail"), ("parsons.notifications.slack", "Slack"), diff --git a/parsons/newmode/__init__.py b/parsons/newmode/__init__.py index 161b0a1cc6..b753baba12 100644 --- a/parsons/newmode/__init__.py +++ b/parsons/newmode/__init__.py @@ -1,3 +1,4 @@ from parsons.newmode.newmode import Newmode +from parsons.newmode.newmode_v2 import NewmodeV2 -__all__ = ["Newmode"] +__all__ = ["Newmode", "NewmodeV2"] From 922e89a84e0df508ae1deea3e086c18a8ef13f26 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Fri, 20 Dec 2024 02:41:11 -0800 Subject: [PATCH 02/30] edit connectors --- parsons/newmode/newmode.py | 1 + parsons/newmode/newmode_v2.py | 273 ++++++++++++++++++++++++++++++++++ 2 files changed, 274 insertions(+) create mode 100644 parsons/newmode/newmode_v2.py diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index 7b342528f5..7317e1e70a 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -22,6 +22,7 @@ def __init__(self, api_user=None, api_password=None, api_version=None): Returns: Newmode class """ + logger.warning("Newmode V1 API will be sunset in Feburary 2025.") self.api_user = check_env.check("NEWMODE_API_USER", api_user) self.api_password = check_env.check("NEWMODE_API_PASSWORD", api_password) diff --git a/parsons/newmode/newmode_v2.py b/parsons/newmode/newmode_v2.py new file mode 100644 index 0000000000..1b5ce071b5 --- /dev/null +++ b/parsons/newmode/newmode_v2.py @@ -0,0 +1,273 @@ +from parsons.utilities.oauth_api_connector import OAuth2APIConnector +from parsons.utilities import check_env +from parsons import Table +import logging + +logger = logging.getLogger(__name__) + +API_URL = "https://base.newmode.net/api/" +API_AUTH_URL = "https://base.newmode.net/oauth/token/" +API_CAMPAIGNS_URL = "https://base.newmode.net/" + + +class NewmodeV2(object): + """ + Instantiate Class + `Args`: + api_user: str + The username to use for the API requests. Not required if ``NEWMODE_API_URL`` + env variable set. + api_password: str + The password to use for the API requests. Not required if ``NEWMODE_API_PASSWORD`` + env variable set. + api_version: str + The api version to use. Defaults to v1.0 + Returns: + NewMode Class + """ + + def __init__( + self, + client_id=None, + client_secret=None, + api_version="v2.1", + ): + self.api_version = check_env.check("NEWMODE_API_VERSION", api_version) + self.base_url = API_URL + self.client_id = check_env.check("NEWMODE_API_CLIENT_ID", client_id) + self.__client_secret = check_env.check( + "NEWMODE_API_CLIENT_SECRET", client_secret + ) + self.headers = {"content-type": "application/json"} + + def convert_to_table(self, data): + """Internal method to create a Parsons table from a data element.""" + table = None + if type(data) is list: + table = Table(data) + else: + table = Table([data]) + + return table + + def base_request(self, method, url, data=None, json=None, params={}): + # response = None + # if method == "GET": + # url, req_type, json=None, data=None, params=None) + # try: + response = self.client.request( + url=url, req_type=method, json=json, data=data, params=params + ) + response.raise_for_status() # Raise an exception for bad status codes + + # except Exception as e: + # print("Request failed:") + # print("URL:", response.request.url) + # print("Method:", response.request.method) + # print("Headers:", response.request.headers) + # print("Body:", response.request.body) + # print("Error:", e) + # print(f"url: {response.request.url}") + # elif method == "POST": + # response = self.client.post_request( + # url=url, params=params, json=json, data=data + # ) + # print(response) + # Validate the response and lift up an errors. + success_codes = [200, 201, 202, 204] + self.client.validate_response(response) + if response.status_code in success_codes: + if self.client.json_check(response): + return response.json() + else: + return response.status_code + return response + + def converted_request( + self, + endpoint, + method, + supports_version=True, + data=None, + json=None, + params={}, + convert_to_table=True, + ): + self.client = OAuth2APIConnector( + uri=self.base_url, + auto_refresh_url=API_AUTH_URL, + client_id=self.client_id, + client_secret=self.__client_secret, + headers=self.headers, + token_url=API_AUTH_URL, + grant_type="client_credentials", + ) + + url = f"{self.api_version}/{endpoint}" if supports_version else endpoint + response = self.base_request( + method=method, + url=url, + json=json, + data=data, + params=params, + ) + if not response: + logging.warning(f"Empty result returned from endpoint: {endpoint}") + if convert_to_table: + return self.convert_to_table(response) + else: + return response + + def get_campaign(self, campaign_id, params={}): + """ + Retrieve a specific campaign by ID. + + In v2, a campaign is equivalent to Tools or Actions in V1. + `Args:` + campaign_id: str + The ID of the campaign to retrieve. + params: dict + Query parameters to include in the request. + `Returns:` + Parsons Table containing campaign data. + """ + endpoint = f"/campaign/{campaign_id}/form" + response = self.converted_request( + endpoint=endpoint, + method="GET", + params=params, + ) + return response + + def get_campaigns(self, params={}): + """ + Retrieve all campaigns + In v2, a campaign is equivalent to Tools or Actions in V1. + `Args:` + organization_id: str + ID of organization + params: dict + Query parameters to include in the request. + `Returns:` + Parsons Table containing campaigns data. + """ + self.base_url = API_CAMPAIGNS_URL + self.api_version = "jsonapi" + self.headers = { + "content-type": "application/vnd.api+json", + "accept": "application/vnd.api+json", + "authorization": "Bearer 1234567890", + } + endpoint = "action/action" + response = self.converted_request( + endpoint=endpoint, + method="GET", + params=params, + ) + return response + + def get_recipient( + self, + campaign_id, + street_address=None, + city=None, + postal_code=None, + region=None, + params={}, + ): + """ + Retrieve a specific recipient by ID + `Args:` + campaign_id: str + The ID of the campaign to retrieve. + street_address: str + Street address of recipient + city: str + City of recipient + postal_code: str + Postal code of recipient + region: str + Region (i.e. state/province abbreviation) of recipient + params: dict + Query parameters to include in the request. + `Returns:` + Parsons Table containing recipient data. + """ + address_params = { + "street_address": street_address, + "city": city, + "postal_code": postal_code, + "region": region, + } + if all(x is None for x in address_params.values()): + logger.error( + "Please specify a street address, city, postal code, and/or region." + ) + raise Exception("Incomplete Request") + + params = { + f"address[value][{key}]": value + for key, value in address_params.items() + if value + } + response = self.converted_request( + endpoint=f"campaign/{campaign_id}/target", + method="GET", + params=params, + ) + return response + + def run_submit(self, campaign_id, json=None, data=None, params={}): + """ + V2 only + Pass a submission from a supporter to a campaign + that ultimately fills in a petition, + sends an email or triggers a phone call + depending on your campaign type + + `Args:` + campaign_id: str + The ID of the campaign to retrieve. + params: dict + Query parameters to include in the request. + `Returns:` + Parsons Table containing submit data. + """ + + json = { + "action_id": campaign_id, + "first_name": "TestFirstName", + "last_name": "TestLastName", + "email": "test_abc@test.com", + "opt_in": 1, + "address": {"postal_code": "V6A 2T2"}, + "subject": "This is my subject", + "message": "This is my letter", + } + + response = self.converted_request( + endpoint=f"campaign/{campaign_id}/submit", + method="POST", + data=data, + json=json, + params=params, + convert_to_table=False, + ) + return response + + def get_submissions(self, params={}): + """ + Retrieve and sort submission and contact data + for your organization using a range of filters + that include campaign id, data range and submission status + + `Args:` + params: dict + Query parameters to include in the request. + `Returns:` + Parsons Table containing submit data. + """ + response = self.converted_request( + endpoint="submission", method="GET", params=params + ) + return response From 9a0bf4a3e8639303927f4cafb39d6f50fc632b46 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Fri, 20 Dec 2024 02:41:21 -0800 Subject: [PATCH 03/30] add tests --- test/test_newmode/test_newmode_v2.py | 980 +++++++++++++++++++++++++++ 1 file changed, 980 insertions(+) create mode 100644 test/test_newmode/test_newmode_v2.py diff --git a/test/test_newmode/test_newmode_v2.py b/test/test_newmode/test_newmode_v2.py new file mode 100644 index 0000000000..ef67e2f535 --- /dev/null +++ b/test/test_newmode/test_newmode_v2.py @@ -0,0 +1,980 @@ +import unittest +from test.utils import assert_matching_tables + +import requests_mock +from parsons import Table, NewmodeV2 + +CLIENT_ID = "fakeClientID" +CLIENT_SECRET = "fakeClientSecret" + + +API_URL = "https://base.newmode.net/api/" +API_AUTH_URL = "https://base.newmode.net/oauth/token/" +API_CAMPAIGNS_URL = "https://base.newmode.net/" + + +class TestZoom(unittest.TestCase): + @requests_mock.Mocker() + def setUp(self, m): + m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) + self.nm = NewmodeV2(CLIENT_ID, CLIENT_SECRET) + + @requests_mock.Mocker() + def test_get_campaign(self, m): + campaign_id= "fakeCampaignID" + result={'contact': {'id': [{'value': 26237005}], 'uuid': [{'value': 'b897e271-94ef-4e21-96eb-08919c3cf96a'}], 'revision_id': [{'value': 307400}], 'org_id': [{'value': 2125}], 'honorific': [], 'first_name': [{'value': 'TestFirstName'}], 'last_name': [{'value': 'TestLastName'}], 'name_suffix': [], 'email': [{'value': 'test_abc@test.com'}], 'mobile_phone': [], 'alternate_phone': [], 'twitter_handle': [], 'street_address': [], 'city': [], 'region': [], 'country': [], 'postal_code': [{'value': 'V6A 2T2'}], 'latitude': [], 'longitude': [], 'opt_in': [{'value': True}], 'nm_product_opt_in': [{'value': False}], 'nm_marketing_opt_in': [{'value': False}], 'groups': [{'target_id': 1189, 'target_type': 'contact_group', 'target_uuid': '0ad63afe-703c-4df3-8c4f-b2fe2183497b', 'url': '/contact-group/1189'}], 'created': [{'value': '1734674126', 'format': 'Y-m-d\\TH:i:sP'}], 'changed': [{'value': '1734674126', 'format': 'Y-m-d\\TH:i:sP'}], 'prefill_hash': [{'value': '90dfa191e412370e911554ab36d010e98527e0f058ea9d6f4affad5ebd6bd328'}], 'subscriber': [], 'sync_status': [[]], 'entitygroupfield': [{'target_id': 2664911, 'target_type': 'group_content', 'target_uuid': '95631305-d57a-467a-af88-d549daf471fe', 'url': '/group/2125/content/2664911'}]}, 'links': {'Facebook': {'label': 'Share on Facebook', 'url': 'https://www.facebook.com/sharer.php?s=100&u=https://win.newmode.net/themovementcooperative/teset', 'title': ''}, 'Twitter': {'label': 'Tweet to your followers', 'url': 'https://nwmd.social/s/twitter/5AuGY1uWfuXNxLKMmQ==/b', 'title': ''}, 'Email': {'label': 'Send an email', 'url': 'https://nwmd.social/s/email/5AuGY1uWfuXNxLKMmQ==/b', 'title': 'Add your voice to this campaign!'}, 'Copy Link': {'label': 'Copy Link', 'url': 'https://nwmd.social/s/copylink/5AuGY1uWfuXNxLKMmQ==/b', 'title': ''}}, 'message': 'Already submitted', 'submission': {'sid': [{'value': 346473}], 'uuid': [{'value': 'a712fb0f-f19f-4455-8585-f3dd95415be1'}], 'revision_id': [{'value': 346473}], 'action_id': [{'target_id': 5003, 'target_type': 'node', 'target_uuid': 'd848035a-f7fd-468a-a977-40b46d7a97b9', 'url': '/node/5003'}], 'contact_id': [{'target_id': 26237005, 'target_type': 'contact', 'target_uuid': 'b897e271-94ef-4e21-96eb-08919c3cf96a', 'url': '/contact/26237005'}], 'status': [{'target_id': 78, 'target_type': 'taxonomy_term', 'target_uuid': '1b680fc7-3a53-4790-8865-888e0f5bba19', 'url': '/taxonomy/term/78'}], 'testmode': [{'value': False}], 'edited': [{'value': True}], 'device': [], 'browser': [], 'browser_version': [], 'os': [], 'os_version': [], 'parent_url': [], 'source_code': [], 'search_value': [], 'created': [{'value': '1734674126', 'format': 'Y-m-d\\TH:i:sP'}], 'changed': [{'value': '1734674126', 'format': 'Y-m-d\\TH:i:sP'}], 'entitygroupfield': [{'target_id': 2664913, 'target_type': 'group_content', 'target_uuid': 'fb99d1d0-a329-4354-9f8d-9b236e751714', 'url': '/group/2125/content/2664913'}]}, 'ref_id': '90dfa191e412370e911554ab36d010e98527e0f058ea9d6f4affad5ebd6bd328'} + tbl = Table( + [ + { + "id": "C5A2nRWwTMm_hXyJb1JXMh", + "first_name": "Bob", + "last_name": "McBob", + "email": "bob@bob.com", + "type": 2, + "pmi": 8374523641, + "timezone": "America/New_York", + "verified": 1, + "dept": "", + "created_at": "2017-10-06T15:22:34Z", + "last_login_time": "2020-05-06T16:50:45Z", + "last_client_version": "", + "language": "", + "phone_number": "", + "status": "active", + } + ] + ) + + m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) + m.get(API_URL + "users", json=user_json) + assert_matching_tables(self.zoom.get_users(), tbl) + + @requests_mock.Mocker() + def test_get_meeting_participants(self, m): + participants = { + "page_count": 1, + "page_size": 30, + "total_records": 4, + "next_page_token": "", + "participants": [ + { + "id": "", + "user_id": "16778240", + "name": "Barack Obama", + "user_email": "", + "join_time": "2020-04-24T21:00:26Z", + "leave_time": "2020-04-24T22:24:38Z", + "duration": 5052, + "attentiveness_score": "", + }, + { + "id": "", + "user_id": "16779264", + "name": "", + "user_email": "", + "join_time": "2020-04-24T21:00:45Z", + "leave_time": "2020-04-24T22:24:38Z", + "duration": 5033, + "attentiveness_score": "", + }, + ], + } + + tbl = Table( + [ + { + "id": "", + "user_id": "16778240", + "name": "Barack Obama", + "user_email": "", + "join_time": "2020-04-24T21:00:26Z", + "leave_time": "2020-04-24T22:24:38Z", + "duration": 5052, + "attentiveness_score": "", + }, + { + "id": "", + "user_id": "16779264", + "name": "", + "user_email": "", + "join_time": "2020-04-24T21:00:45Z", + "leave_time": "2020-04-24T22:24:38Z", + "duration": 5033, + "attentiveness_score": "", + }, + ] + ) + + m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) + m.get(API_URL + "report/meetings/123/participants", json=participants) + assert_matching_tables(self.zoom.get_past_meeting_participants(123), tbl) + + @requests_mock.Mocker() + def test_get_meeting_registrants(self, m): + registrants = { + "page_count": 1, + "page_size": 30, + "total_records": 4, + "next_page_token": "", + "registrants": [ + { + "id": "", + "user_id": "16778240", + "name": "Barack Obama", + "user_email": "", + "purchasing_time_frame": "Within a month", + "role_in_purchase_process": "Not involved", + }, + { + "id": "", + "user_id": "16779264", + "name": "", + "user_email": "", + "purchasing_time_frame": "Within a month", + "role_in_purchase_process": "Not involved", + }, + ], + } + + tbl = Table( + [ + { + "id": "", + "user_id": "16778240", + "name": "Barack Obama", + "user_email": "", + "purchasing_time_frame": "Within a month", + "role_in_purchase_process": "Not involved", + }, + { + "id": "", + "user_id": "16779264", + "name": "", + "user_email": "", + "purchasing_time_frame": "Within a month", + "role_in_purchase_process": "Not involved", + }, + ] + ) + + m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) + m.get(API_URL + "meetings/123/registrants", json=registrants) + assert_matching_tables(self.zoom.get_meeting_registrants(123), tbl) + + @requests_mock.Mocker() + def test_get_user_webinars(self, m): + webinars = { + "page_count": 1, + "page_size": 30, + "total_records": 4, + "next_page_token": "", + "webinars": [ + { + "uuid": "dsghfkhaewfds", + "id": "", + "host_id": "24654130000000", + "topic": "My Webinar", + "agenda": "Learn more about Zoom APIs", + "type": "5", + "duration": "60", + "start_time": "2019-09-24T22:00:00Z", + "timezone": "America/Los_Angeles", + "created_at": "2019-08-30T22:00:00Z", + "join_url": "https://zoom.us/0001000/awesomewebinar", + }, + { + "uuid": "dhf8as7dhf", + "id": "", + "host_id": "24654130000345", + "topic": "My Webinar", + "agenda": "Learn more about Zoom APIs", + "type": "5", + "duration": "60", + "start_time": "2019-09-24T22:00:00Z", + "timezone": "America/Los_Angeles", + "created_at": "2019-08-30T22:00:00Z", + "join_url": "https://zoom.us/0001000/awesomewebinar", + }, + ], + } + + tbl = Table( + [ + { + "uuid": "dsghfkhaewfds", + "id": "", + "host_id": "24654130000000", + "topic": "My Webinar", + "agenda": "Learn more about Zoom APIs", + "type": "5", + "duration": "60", + "start_time": "2019-09-24T22:00:00Z", + "timezone": "America/Los_Angeles", + "created_at": "2019-08-30T22:00:00Z", + "join_url": "https://zoom.us/0001000/awesomewebinar", + }, + { + "uuid": "dhf8as7dhf", + "id": "", + "host_id": "24654130000345", + "topic": "My Webinar", + "agenda": "Learn more about Zoom APIs", + "type": "5", + "duration": "60", + "start_time": "2019-09-24T22:00:00Z", + "timezone": "America/Los_Angeles", + "created_at": "2019-08-30T22:00:00Z", + "join_url": "https://zoom.us/0001000/awesomewebinar", + }, + ] + ) + + m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) + m.get(API_URL + "users/123/webinars", json=webinars) + assert_matching_tables(self.zoom.get_user_webinars(123), tbl) + + @requests_mock.Mocker() + def test_get_past_webinar_participants(self, m): + participants = { + "page_count": 1, + "page_size": 30, + "total_records": 4, + "next_page_token": "", + "participants": [ + { + "id": "", + "user_id": "sdfjkldsf87987", + "name": "Barack", + "user_email": "riya@sdfjkldsf87987.fdjfhdf", + "join_time": "2019-02-01T12:34:12.660Z", + "leave_time": "2019-03-01T12:34:12.660Z", + "duration": "20", + }, + { + "id": "", + "user_id": "sdfjkldsfdfgdfg", + "name": "Joe", + "user_email": "riya@sdfjkldsf87987.fdjfhdf", + "join_time": "2019-02-01T12:34:12.660Z", + "leave_time": "2019-03-01T12:34:12.660Z", + "duration": "20", + }, + ], + } + + tbl = Table( + [ + { + "id": "", + "user_id": "sdfjkldsf87987", + "name": "Barack", + "user_email": "riya@sdfjkldsf87987.fdjfhdf", + "join_time": "2019-02-01T12:34:12.660Z", + "leave_time": "2019-03-01T12:34:12.660Z", + "duration": "20", + }, + { + "id": "", + "user_id": "sdfjkldsfdfgdfg", + "name": "Joe", + "user_email": "riya@sdfjkldsf87987.fdjfhdf", + "join_time": "2019-02-01T12:34:12.660Z", + "leave_time": "2019-03-01T12:34:12.660Z", + "duration": "20", + }, + ] + ) + + m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) + m.get(API_URL + "report/webinars/123/participants", json=participants) + assert_matching_tables(self.zoom.get_past_webinar_participants(123), tbl) + + @requests_mock.Mocker() + def test_get_past_webinar_report(self, m): + report = { + "custom_keys": [{"key": "Host Nation", "value": "US"}], + "dept": "HR", + "duration": 2, + "end_time": "2022-03-15T07:42:22Z", + "id": 345678902224, + "participants_count": 4, + "start_time": "2022-03-15T07:40:46Z", + "topic": "My Meeting", + "total_minutes": 3, + "tracking_fields": [{"field": "Host Nation", "value": "US"}], + "type": 4, + "user_email": "jchill@example.com", + "user_name": "Jill Chill", + "uuid": "4444AAAiAAAAAiAiAiiAii==", + } + + m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) + m.get(API_URL + "report/webinars/123", json=report) + assert_matching_tables(self.zoom.get_past_webinar_report(123), report) + + @requests_mock.Mocker() + def test_get_webinar_registrants(self, m): + registrants = { + "page_count": 1, + "page_size": 30, + "total_records": 4, + "next_page_token": "", + "registrants": [ + { + "id": "", + "email": "barack@obama.com", + "first_name": "Barack", + "last_name": "Obama", + "address": "dsfhkdjsfh st", + "city": "jackson heights", + "country": "US", + "zip": "11371", + "state": "NY", + "phone": "00000000", + "industry": "Food", + "org": "Cooking Org", + "job_title": "Chef", + "purchasing_time_frame": "1-3 months", + "role_in_purchase_process": "Influencer", + "no_of_employees": "10", + "comments": "Looking forward to the Webinar", + "custom_questions": [ + { + "title": "What do you hope to learn from this Webinar?", + "value": "Look forward to learning how you come up with recipes and services", # noqa: E501 + } + ], + "status": "approved", + "create_time": "2019-02-26T23:01:16.899Z", + "join_url": "https://zoom.us/webinar/mywebinariscool", + }, + { + "id": "", + "email": "joe@biden.com", + "first_name": "Joe", + "last_name": "Biden", + "address": "dsfhkdjsfh st", + "city": "jackson heights", + "country": "US", + "zip": "11371", + "state": "NY", + "phone": "00000000", + "industry": "Food", + "org": "Cooking Org", + "job_title": "Chef", + "purchasing_time_frame": "1-3 months", + "role_in_purchase_process": "Influencer", + "no_of_employees": "10", + "comments": "Looking forward to the Webinar", + "custom_questions": [ + { + "title": "What do you hope to learn from this Webinar?", + "value": "Look forward to learning how you come up with recipes and services", # noqa: E501 + } + ], + "status": "approved", + "create_time": "2019-02-26T23:01:16.899Z", + "join_url": "https://zoom.us/webinar/mywebinariscool", + }, + ], + } + + tbl = Table( + [ + { + "id": "", + "email": "barack@obama.com", + "first_name": "Barack", + "last_name": "Obama", + "address": "dsfhkdjsfh st", + "city": "jackson heights", + "country": "US", + "zip": "11371", + "state": "NY", + "phone": "00000000", + "industry": "Food", + "org": "Cooking Org", + "job_title": "Chef", + "purchasing_time_frame": "1-3 months", + "role_in_purchase_process": "Influencer", + "no_of_employees": "10", + "comments": "Looking forward to the Webinar", + "custom_questions": [ + { + "title": "What do you hope to learn from this Webinar?", + "value": "Look forward to learning how you come up with recipes and services", # noqa: E501 + } + ], + "status": "approved", + "create_time": "2019-02-26T23:01:16.899Z", + "join_url": "https://zoom.us/webinar/mywebinariscool", + }, + { + "id": "", + "email": "joe@biden.com", + "first_name": "Joe", + "last_name": "Biden", + "address": "dsfhkdjsfh st", + "city": "jackson heights", + "country": "US", + "zip": "11371", + "state": "NY", + "phone": "00000000", + "industry": "Food", + "org": "Cooking Org", + "job_title": "Chef", + "purchasing_time_frame": "1-3 months", + "role_in_purchase_process": "Influencer", + "no_of_employees": "10", + "comments": "Looking forward to the Webinar", + "custom_questions": [ + { + "title": "What do you hope to learn from this Webinar?", + "value": "Look forward to learning how you come up with recipes and services", # noqa: E501 + } + ], + "status": "approved", + "create_time": "2019-02-26T23:01:16.899Z", + "join_url": "https://zoom.us/webinar/mywebinariscool", + }, + ] + ) + + m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) + m.get(API_URL + "webinars/123/registrants", json=registrants) + assert_matching_tables(self.zoom.get_webinar_registrants(123), tbl) + + @requests_mock.Mocker() + def test_get_meeting_poll_metadata(self, m): + poll = { + "id": 1, + "status": "started", + "anonymous": "False", + "poll_type": 1, + "questions": [ + { + "answer_max_character": 1, + "answer_min_character": 1, + "answer_required": "False", + "answers": "Larry David's Curb Your Enthusiasm", + "case_sensitive": "True", + "name": "Secret Truth", + "prompts": [ + { + "prompt_question": "What's the secret truth of the universe?", + "prompt_right_answers": [ + "Pizza delivery", + "Larry David's Curb Your Enthusiasm", + ], + } + ], + "rating_max_label": "", + "rating_max_value": 1, + "rating_min_label": "", + "rating_min_value": 0, + "right_answers": "", + "show_as_dropdown": False, + "type": "short_answer", + } + ], + } + + tbl = Table( + [ + { + "answer_max_character": 1, + "answer_min_character": 1, + "answer_required": "False", + "answers": "Larry David's Curb Your Enthusiasm", + "case_sensitive": "True", + "name": "Secret Truth", + "rating_max_label": "", + "rating_max_value": 1, + "rating_min_label": "", + "rating_min_value": 0, + "right_answers": "", + "show_as_dropdown": False, + "type": "short_answer", + "prompts__prompt_question": "What's the secret truth of the universe?", + "prompts__prompt_right_answers": [ + "Pizza delivery", + "Larry David's Curb Your Enthusiasm", + ], + } + ], + ) + + m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) + m.get(API_URL + "meetings/123/polls/1", json=poll) + assert_matching_tables(self.zoom.get_meeting_poll_metadata(123, 1), tbl) + + @requests_mock.Mocker() + def test_get_meeting_all_polls_metadata(self, m): + polls = { + "polls": [ + { + "id": 1, + "status": "started", + "anonymous": "False", + "poll_type": 1, + "questions": [ + { + "answer_max_character": 1, + "answer_min_character": 1, + "answer_required": "False", + "answers": "Larry David's Curb Your Enthusiasm", + "case_sensitive": "True", + "name": "Secret Truth", + "prompts": [ + { + "prompt_question": "What's the secret truth of the universe?", + "prompt_right_answers": [ + "Pizza delivery", + "Larry David's Curb Your Enthusiasm", + ], + } + ], + "rating_max_label": "", + "rating_max_value": 1, + "rating_min_label": "", + "rating_min_value": 0, + "right_answers": "", + "show_as_dropdown": False, + "type": "short_answer", + } + ], + }, + { + "id": 2, + "status": "started", + "anonymous": "False", + "poll_type": 1, + "questions": [ + { + "answer_max_character": 1, + "answer_min_character": 1, + "answer_required": "False", + "answers": "Mets", + "case_sensitive": "True", + "name": "Play ball", + "prompts": [ + { + "prompt_question": "Best NY baseball team?", + "prompt_right_answers": ["Mets"], + } + ], + "rating_max_label": "", + "rating_max_value": 1, + "rating_min_label": "", + "rating_min_value": 0, + "right_answers": "", + "show_as_dropdown": True, + "type": "short_answer", + } + ], + }, + ] + } + + tbl = Table( + [ + { + "id": 1, + "status": "started", + "anonymous": "False", + "poll_type": 1, + "questions__answer_max_character": 1, + "questions__answer_min_character": 1, + "questions__answer_required": "False", + "questions__answers": "Larry David's Curb Your Enthusiasm", + "questions__case_sensitive": "True", + "questions__name": "Secret Truth", + "questions__prompts": [ + { + "prompt_question": "What's the secret truth of the universe?", + "prompt_right_answers": [ + "Pizza delivery", + "Larry David's Curb Your Enthusiasm", + ], + } + ], + "questions__rating_max_label": "", + "questions__rating_max_value": 1, + "questions__rating_min_label": "", + "questions__rating_min_value": 0, + "questions__right_answers": "", + "questions__show_as_dropdown": False, + "questions__type": "short_answer", + }, + { + "id": 2, + "status": "started", + "anonymous": "False", + "poll_type": 1, + "questions__answer_max_character": 1, + "questions__answer_min_character": 1, + "questions__answer_required": "False", + "questions__answers": "Mets", + "questions__case_sensitive": "True", + "questions__name": "Play ball", + "questions__prompts": [ + { + "prompt_question": "Best NY baseball team?", + "prompt_right_answers": ["Mets"], + } + ], + "questions__rating_max_label": "", + "questions__rating_max_value": 1, + "questions__rating_min_label": "", + "questions__rating_min_value": 0, + "questions__right_answers": "", + "questions__show_as_dropdown": True, + "questions__type": "short_answer", + }, + ] + ) + + m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) + m.get(API_URL + "meetings/123/polls", json=polls) + assert_matching_tables(self.zoom.get_meeting_all_polls_metadata(123), tbl) + + @requests_mock.Mocker() + def test_get_past_meeting_poll_metadata(self, m): + poll = { + "id": 1, + "status": "started", + "anonymous": "False", + "poll_type": 1, + "questions": [ + { + "answer_max_character": 1, + "answer_min_character": 1, + "answer_required": "False", + "answers": "Larry David's Curb Your Enthusiasm", + "case_sensitive": "True", + "name": "Secret Truth", + "prompts": [ + { + "prompt_question": "What's the secret truth of the universe?", + "prompt_right_answers": [ + "Pizza delivery", + "Larry David's Curb Your Enthusiasm", + ], + } + ], + "rating_max_label": "", + "rating_max_value": 1, + "rating_min_label": "", + "rating_min_value": 0, + "right_answers": "", + "show_as_dropdown": False, + "type": "short_answer", + } + ], + } + + tbl = Table( + [ + { + "answer_max_character": 1, + "answer_min_character": 1, + "answer_required": "False", + "answers": "Larry David's Curb Your Enthusiasm", + "case_sensitive": "True", + "name": "Secret Truth", + "rating_max_label": "", + "rating_max_value": 1, + "rating_min_label": "", + "rating_min_value": 0, + "right_answers": "", + "show_as_dropdown": False, + "type": "short_answer", + "prompts__prompt_question": "What's the secret truth of the universe?", + "prompts__prompt_right_answers": [ + "Pizza delivery", + "Larry David's Curb Your Enthusiasm", + ], + } + ] + ) + + m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) + m.get(API_URL + "past_meetings/123/polls", json=poll) + assert_matching_tables(self.zoom.get_past_meeting_poll_metadata(123), tbl) + + @requests_mock.Mocker() + def test_get_webinar_poll_metadata(self, m): + poll = { + "id": "QalIoKWLTJehBJ8e1xRrbQ", + "status": "notstart", + "anonymous": True, + "poll_type": 2, + "questions": [ + { + "answer_max_character": 200, + "answer_min_character": 1, + "answer_required": False, + "answers": ["Extremely useful"], + "case_sensitive": False, + "name": "How useful was this meeting?", + "prompts": [ + { + "prompt_question": "How are you?", + "prompt_right_answers": ["Good"], + } + ], + "rating_max_label": "Extremely Likely", + "rating_max_value": 4, + "rating_min_label": "Not likely", + "rating_min_value": 0, + "right_answers": ["Good"], + "show_as_dropdown": False, + "type": "single", + } + ], + "title": "Learn something new", + } + + tbl = Table( + [ + { + "answer_max_character": 200, + "answer_min_character": 1, + "answer_required": False, + "answers": ["Extremely useful"], + "case_sensitive": False, + "name": "How useful was this meeting?", + "rating_max_label": "Extremely Likely", + "rating_max_value": 4, + "rating_min_label": "Not likely", + "rating_min_value": 0, + "right_answers": ["Good"], + "show_as_dropdown": False, + "type": "single", + "prompts__prompt_question": "How are you?", + "prompts__prompt_right_answers": ["Good"], + } + ] + ) + + m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) + m.get(API_URL + "webinars/123/polls/456", json=poll) + assert_matching_tables(self.zoom.get_webinar_poll_metadata(123, 456), tbl) + + @requests_mock.Mocker() + def test_get_webinar_all_polls_metadata(self, m): + polls = { + "polls": [ + { + "id": "QalIoKWLTJehBJ8e1xRrbQ", + "status": "notstart", + "anonymous": True, + "poll_type": 2, + "questions": [ + { + "answer_max_character": 200, + "answer_min_character": 1, + "answer_required": False, + "answers": ["Extremely useful"], + "case_sensitive": False, + "name": "How useful was this meeting?", + "prompts": [ + { + "prompt_question": "How are you?", + "prompt_right_answers": ["Good"], + } + ], + "rating_max_label": "Extremely Likely", + "rating_max_value": 4, + "rating_min_label": "Not likely", + "rating_min_value": 0, + "right_answers": ["Good"], + "show_as_dropdown": False, + "type": "single", + } + ], + "title": "Learn something new", + } + ], + "total_records": 1, + } + + tbl = Table( + [ + { + "id": "QalIoKWLTJehBJ8e1xRrbQ", + "status": "notstart", + "anonymous": True, + "poll_type": 2, + "title": "Learn something new", + "questions__answer_max_character": 200, + "questions__answer_min_character": 1, + "questions__answer_required": False, + "questions__answers": ["Extremely useful"], + "questions__case_sensitive": False, + "questions__name": "How useful was this meeting?", + "questions__prompts": [ + { + "prompt_question": "How are you?", + "prompt_right_answers": ["Good"], + } + ], + "questions__rating_max_label": "Extremely Likely", + "questions__rating_max_value": 4, + "questions__rating_min_label": "Not likely", + "questions__rating_min_value": 0, + "questions__right_answers": ["Good"], + "questions__show_as_dropdown": False, + "questions__type": "single", + } + ] + ) + + m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) + m.get(API_URL + "webinars/123/polls", json=polls) + assert_matching_tables(self.zoom.get_webinar_all_polls_metadata(123), tbl) + + @requests_mock.Mocker() + def test_get_past_webinar_poll_metadata(self, m): + poll = { + "id": "QalIoKWLTJehBJ8e1xRrbQ", + "status": "notstart", + "anonymous": True, + "poll_type": 2, + "questions": [ + { + "answer_max_character": 200, + "answer_min_character": 1, + "answer_required": False, + "answers": ["Extremely useful"], + "case_sensitive": False, + "name": "How useful was this meeting?", + "prompts": [ + { + "prompt_question": "How are you?", + "prompt_right_answers": ["Good"], + } + ], + "rating_max_label": "Extremely Likely", + "rating_max_value": 4, + "rating_min_label": "Not likely", + "rating_min_value": 0, + "right_answers": ["Good"], + "show_as_dropdown": False, + "type": "single", + } + ], + "title": "Learn something new", + } + + tbl = Table( + [ + { + "answer_max_character": 200, + "answer_min_character": 1, + "answer_required": False, + "answers": ["Extremely useful"], + "case_sensitive": False, + "name": "How useful was this meeting?", + "rating_max_label": "Extremely Likely", + "rating_max_value": 4, + "rating_min_label": "Not likely", + "rating_min_value": 0, + "right_answers": ["Good"], + "show_as_dropdown": False, + "type": "single", + "prompts__prompt_question": "How are you?", + "prompts__prompt_right_answers": ["Good"], + } + ], + ) + + m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) + m.get(API_URL + "past_webinars/123/polls", json=poll) + assert_matching_tables(self.zoom.get_past_webinar_poll_metadata(123), tbl) + + @requests_mock.Mocker() + def test_get_meeting_poll_results(self, m): + poll = { + "id": 123456, + "uuid": "4444AAAiAAAAAiAiAiiAii==", + "start_time": "2022-02-01T12:34:12.660Z", + "questions": [ + { + "email": "jchill@example.com", + "name": "Jill Chill", + "first_name": "Jill", + "last_name": "Chill", + "question_details": [ + { + "answer": "I am wonderful.", + "date_time": "2022-02-01T12:37:12.660Z", + "polling_id": "798fGJEWrA", + "question": "How are you?", + } + ], + } + ], + } + + tbl = Table( + [ + { + "email": "jchill@example.com", + "name": "Jill Chill", + "first_name": "Jill", + "last_name": "Chill", + "answer": "I am wonderful.", + "date_time": "2022-02-01T12:37:12.660Z", + "polling_id": "798fGJEWrA", + "question": "How are you?", + } + ] + ) + + m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) + m.get(API_URL + "report/meetings/123/polls", json=poll) + assert_matching_tables(self.zoom.get_meeting_poll_results(123), tbl) + + @requests_mock.Mocker() + def test_get_webinar_poll_results(self, m): + poll = { + "id": 123456, + "questions": [ + { + "email": "jchill@example.com", + "name": "Jill Chill", + "first_name": "Jill", + "last_name": "Chill", + "question_details": [ + { + "answer": "I am wonderful.", + "date_time": "2022-02-01T12:37:12.660Z", + "polling_id": "798fGJEWrA", + "question": "How are you?", + } + ], + } + ], + "start_time": "2022-02-01T12:34:12.660Z", + "uuid": "4444AAAiAAAAAiAiAiiAii==", + } + + tbl = Table( + [ + { + "email": "jchill@example.com", + "name": "Jill Chill", + "first_name": "Jill", + "last_name": "Chill", + "answer": "I am wonderful.", + "date_time": "2022-02-01T12:37:12.660Z", + "polling_id": "798fGJEWrA", + "question": "How are you?", + } + ] + ) + + m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) + m.get(API_URL + "report/webinars/123/polls", json=poll) + assert_matching_tables(self.zoom.get_webinar_poll_results(123), tbl) From 03735d352acdef75b99841105229e97d47dcc585 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Fri, 20 Dec 2024 13:45:47 -0800 Subject: [PATCH 04/30] add v2 --- parsons/newmode/newmode_v2.py | 114 +-- test/test_newmode/test_newmode_v2.py | 1221 +++++++------------------- 2 files changed, 338 insertions(+), 997 deletions(-) diff --git a/parsons/newmode/newmode_v2.py b/parsons/newmode/newmode_v2.py index 1b5ce071b5..17aa64fb46 100644 --- a/parsons/newmode/newmode_v2.py +++ b/parsons/newmode/newmode_v2.py @@ -11,20 +11,6 @@ class NewmodeV2(object): - """ - Instantiate Class - `Args`: - api_user: str - The username to use for the API requests. Not required if ``NEWMODE_API_URL`` - env variable set. - api_password: str - The password to use for the API requests. Not required if ``NEWMODE_API_PASSWORD`` - env variable set. - api_version: str - The api version to use. Defaults to v1.0 - Returns: - NewMode Class - """ def __init__( self, @@ -32,6 +18,20 @@ def __init__( client_secret=None, api_version="v2.1", ): + """ + Instantiate Class + `Args`: + api_user: str + The username to use for the API requests. Not required if ``NEWMODE_API_URL`` + env variable set. + api_password: str + The password to use for the API requests. Not required if ``NEWMODE_API_PASSWORD`` + env variable set. + api_version: str + The api version to use. Defaults to v1.0 + Returns: + NewMode Class + """ self.api_version = check_env.check("NEWMODE_API_VERSION", api_version) self.base_url = API_URL self.client_id = check_env.check("NEWMODE_API_CLIENT_ID", client_id) @@ -50,35 +50,19 @@ def convert_to_table(self, data): return table - def base_request(self, method, url, data=None, json=None, params={}): - # response = None - # if method == "GET": - # url, req_type, json=None, data=None, params=None) - # try: + def base_request(self, method, url, data=None, json=None, data_key=None, params={}): + response = None response = self.client.request( url=url, req_type=method, json=json, data=data, params=params ) - response.raise_for_status() # Raise an exception for bad status codes - - # except Exception as e: - # print("Request failed:") - # print("URL:", response.request.url) - # print("Method:", response.request.method) - # print("Headers:", response.request.headers) - # print("Body:", response.request.body) - # print("Error:", e) - # print(f"url: {response.request.url}") - # elif method == "POST": - # response = self.client.post_request( - # url=url, params=params, json=json, data=data - # ) - # print(response) - # Validate the response and lift up an errors. + response.raise_for_status() + print("URL:", response.request.url) success_codes = [200, 201, 202, 204] self.client.validate_response(response) if response.status_code in success_codes: if self.client.json_check(response): - return response.json() + response_json = response.json() + return response_json[data_key] if data_key else response_json else: return response.status_code return response @@ -92,6 +76,7 @@ def converted_request( json=None, params={}, convert_to_table=True, + data_key=None, ): self.client = OAuth2APIConnector( uri=self.base_url, @@ -110,6 +95,7 @@ def converted_request( json=json, data=data, params=params, + data_key=data_key, ) if not response: logging.warning(f"Empty result returned from endpoint: {endpoint}") @@ -149,7 +135,7 @@ def get_campaigns(self, params={}): params: dict Query parameters to include in the request. `Returns:` - Parsons Table containing campaigns data. + List containing all campaign ids. """ self.base_url = API_CAMPAIGNS_URL self.api_version = "jsonapi" @@ -158,13 +144,11 @@ def get_campaigns(self, params={}): "accept": "application/vnd.api+json", "authorization": "Bearer 1234567890", } - endpoint = "action/action" + endpoint = "node/action" response = self.converted_request( - endpoint=endpoint, - method="GET", - params=params, + endpoint=endpoint, method="GET", params=params, data_key="data" ) - return response + return response["id"] def get_recipient( self, @@ -219,7 +203,6 @@ def get_recipient( def run_submit(self, campaign_id, json=None, data=None, params={}): """ - V2 only Pass a submission from a supporter to a campaign that ultimately fills in a petition, sends an email or triggers a phone call @@ -234,17 +217,6 @@ def run_submit(self, campaign_id, json=None, data=None, params={}): Parsons Table containing submit data. """ - json = { - "action_id": campaign_id, - "first_name": "TestFirstName", - "last_name": "TestLastName", - "email": "test_abc@test.com", - "opt_in": 1, - "address": {"postal_code": "V6A 2T2"}, - "subject": "This is my subject", - "message": "This is my letter", - } - response = self.converted_request( endpoint=f"campaign/{campaign_id}/submit", method="POST", @@ -255,19 +227,21 @@ def run_submit(self, campaign_id, json=None, data=None, params={}): ) return response - def get_submissions(self, params={}): - """ - Retrieve and sort submission and contact data - for your organization using a range of filters - that include campaign id, data range and submission status - - `Args:` - params: dict - Query parameters to include in the request. - `Returns:` - Parsons Table containing submit data. - """ - response = self.converted_request( - endpoint="submission", method="GET", params=params - ) - return response + # TODO: add get_submissions method after updates from NewMode + # def get_submissions(self, campaign_id, params={}): + # """ + # Retrieve and sort submission and contact data + # for your organization using a range of filters + # that include campaign id, data range and submission status + + # `Args:` + # params: dict + # Query parameters to include in the request. + # `Returns:` + # Parsons Table containing submit data. + # """ + # params = {"campaign": campaign_id} + # response = self.converted_request( + # endpoint="submission", method="GET", params=params + # ) + # return response diff --git a/test/test_newmode/test_newmode_v2.py b/test/test_newmode/test_newmode_v2.py index ef67e2f535..6590289c11 100644 --- a/test/test_newmode/test_newmode_v2.py +++ b/test/test_newmode/test_newmode_v2.py @@ -18,963 +18,330 @@ class TestZoom(unittest.TestCase): def setUp(self, m): m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) self.nm = NewmodeV2(CLIENT_ID, CLIENT_SECRET) + self.campaign_id = "fakeCampaignID" + self.api_version = "v2.1" + self.base_url = f"{API_URL}{self.api_version}" @requests_mock.Mocker() def test_get_campaign(self, m): - campaign_id= "fakeCampaignID" - result={'contact': {'id': [{'value': 26237005}], 'uuid': [{'value': 'b897e271-94ef-4e21-96eb-08919c3cf96a'}], 'revision_id': [{'value': 307400}], 'org_id': [{'value': 2125}], 'honorific': [], 'first_name': [{'value': 'TestFirstName'}], 'last_name': [{'value': 'TestLastName'}], 'name_suffix': [], 'email': [{'value': 'test_abc@test.com'}], 'mobile_phone': [], 'alternate_phone': [], 'twitter_handle': [], 'street_address': [], 'city': [], 'region': [], 'country': [], 'postal_code': [{'value': 'V6A 2T2'}], 'latitude': [], 'longitude': [], 'opt_in': [{'value': True}], 'nm_product_opt_in': [{'value': False}], 'nm_marketing_opt_in': [{'value': False}], 'groups': [{'target_id': 1189, 'target_type': 'contact_group', 'target_uuid': '0ad63afe-703c-4df3-8c4f-b2fe2183497b', 'url': '/contact-group/1189'}], 'created': [{'value': '1734674126', 'format': 'Y-m-d\\TH:i:sP'}], 'changed': [{'value': '1734674126', 'format': 'Y-m-d\\TH:i:sP'}], 'prefill_hash': [{'value': '90dfa191e412370e911554ab36d010e98527e0f058ea9d6f4affad5ebd6bd328'}], 'subscriber': [], 'sync_status': [[]], 'entitygroupfield': [{'target_id': 2664911, 'target_type': 'group_content', 'target_uuid': '95631305-d57a-467a-af88-d549daf471fe', 'url': '/group/2125/content/2664911'}]}, 'links': {'Facebook': {'label': 'Share on Facebook', 'url': 'https://www.facebook.com/sharer.php?s=100&u=https://win.newmode.net/themovementcooperative/teset', 'title': ''}, 'Twitter': {'label': 'Tweet to your followers', 'url': 'https://nwmd.social/s/twitter/5AuGY1uWfuXNxLKMmQ==/b', 'title': ''}, 'Email': {'label': 'Send an email', 'url': 'https://nwmd.social/s/email/5AuGY1uWfuXNxLKMmQ==/b', 'title': 'Add your voice to this campaign!'}, 'Copy Link': {'label': 'Copy Link', 'url': 'https://nwmd.social/s/copylink/5AuGY1uWfuXNxLKMmQ==/b', 'title': ''}}, 'message': 'Already submitted', 'submission': {'sid': [{'value': 346473}], 'uuid': [{'value': 'a712fb0f-f19f-4455-8585-f3dd95415be1'}], 'revision_id': [{'value': 346473}], 'action_id': [{'target_id': 5003, 'target_type': 'node', 'target_uuid': 'd848035a-f7fd-468a-a977-40b46d7a97b9', 'url': '/node/5003'}], 'contact_id': [{'target_id': 26237005, 'target_type': 'contact', 'target_uuid': 'b897e271-94ef-4e21-96eb-08919c3cf96a', 'url': '/contact/26237005'}], 'status': [{'target_id': 78, 'target_type': 'taxonomy_term', 'target_uuid': '1b680fc7-3a53-4790-8865-888e0f5bba19', 'url': '/taxonomy/term/78'}], 'testmode': [{'value': False}], 'edited': [{'value': True}], 'device': [], 'browser': [], 'browser_version': [], 'os': [], 'os_version': [], 'parent_url': [], 'source_code': [], 'search_value': [], 'created': [{'value': '1734674126', 'format': 'Y-m-d\\TH:i:sP'}], 'changed': [{'value': '1734674126', 'format': 'Y-m-d\\TH:i:sP'}], 'entitygroupfield': [{'target_id': 2664913, 'target_type': 'group_content', 'target_uuid': 'fb99d1d0-a329-4354-9f8d-9b236e751714', 'url': '/group/2125/content/2664913'}]}, 'ref_id': '90dfa191e412370e911554ab36d010e98527e0f058ea9d6f4affad5ebd6bd328'} - tbl = Table( - [ - { - "id": "C5A2nRWwTMm_hXyJb1JXMh", - "first_name": "Bob", - "last_name": "McBob", - "email": "bob@bob.com", - "type": 2, - "pmi": 8374523641, - "timezone": "America/New_York", - "verified": 1, - "dept": "", - "created_at": "2017-10-06T15:22:34Z", - "last_login_time": "2020-05-06T16:50:45Z", - "last_client_version": "", - "language": "", - "phone_number": "", - "status": "active", - } - ] - ) - - m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - m.get(API_URL + "users", json=user_json) - assert_matching_tables(self.zoom.get_users(), tbl) - - @requests_mock.Mocker() - def test_get_meeting_participants(self, m): - participants = { - "page_count": 1, - "page_size": 30, - "total_records": 4, - "next_page_token": "", - "participants": [ - { - "id": "", - "user_id": "16778240", - "name": "Barack Obama", - "user_email": "", - "join_time": "2020-04-24T21:00:26Z", - "leave_time": "2020-04-24T22:24:38Z", - "duration": 5052, - "attentiveness_score": "", - }, - { - "id": "", - "user_id": "16779264", - "name": "", - "user_email": "", - "join_time": "2020-04-24T21:00:45Z", - "leave_time": "2020-04-24T22:24:38Z", - "duration": 5033, - "attentiveness_score": "", + json_response = { + "contact": { + "id": [{"value": 12345678}], + "uuid": [{"value": "test-uuid"}], + "revision_id": [{"value": 123456}], + "org_id": [{"value": 1234}], + "honorific": ["test"], + "first_name": [{"value": "TestFirstName"}], + "last_name": [{"value": "TestLastName"}], + "name_suffix": ["TestSuffix"], + "email": [{"value": "test_abc@test.com"}], + "mobile_phone": [], + "alternate_phone": [], + "twitter_handle": [], + "street_address": [], + "city": [], + "region": [], + "country": [], + "postal_code": [{"value": "test_postal_code"}], + "latitude": [], + "longitude": [], + "opt_in": [{"value": True}], + "nm_product_opt_in": [{"value": False}], + "nm_marketing_opt_in": [{"value": False}], + "groups": [ + { + "target_id": 1234, + "target_type": "contact_group", + "target_uuid": "test-uuid", + "url": "/contact-group/1234", + } + ], + "created": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], + "changed": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], + "prefill_hash": [{"value": "test-value"}], + "subscriber": [], + "sync_status": [[]], + "entitygroupfield": [ + { + "target_id": 1234567, + "target_type": "group_target", + "target_uuid": "test-value", + "url": "/group/1234/content/1234567", + } + ], + }, + "links": { + "Facebook": { + "label": "Share on Facebook", + "url": "https://www.facebook.com/sharer.php?s=100&u=https://win.newmode.net/test", + "title": "", }, - ], - } - - tbl = Table( - [ - { - "id": "", - "user_id": "16778240", - "name": "Barack Obama", - "user_email": "", - "join_time": "2020-04-24T21:00:26Z", - "leave_time": "2020-04-24T22:24:38Z", - "duration": 5052, - "attentiveness_score": "", - }, - { - "id": "", - "user_id": "16779264", - "name": "", - "user_email": "", - "join_time": "2020-04-24T21:00:45Z", - "leave_time": "2020-04-24T22:24:38Z", - "duration": 5033, - "attentiveness_score": "", + "Twitter": { + "label": "Tweet to your followers", + "url": "https://nwmd.social/s/twitter/test", + "title": "", }, - ] - ) - - m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - m.get(API_URL + "report/meetings/123/participants", json=participants) - assert_matching_tables(self.zoom.get_past_meeting_participants(123), tbl) - - @requests_mock.Mocker() - def test_get_meeting_registrants(self, m): - registrants = { - "page_count": 1, - "page_size": 30, - "total_records": 4, - "next_page_token": "", - "registrants": [ - { - "id": "", - "user_id": "16778240", - "name": "Barack Obama", - "user_email": "", - "purchasing_time_frame": "Within a month", - "role_in_purchase_process": "Not involved", + "Email": { + "label": "Send an email", + "url": "https://nwmd.social/s/email/test", + "title": "Add your voice to this campaign!", }, - { - "id": "", - "user_id": "16779264", - "name": "", - "user_email": "", - "purchasing_time_frame": "Within a month", - "role_in_purchase_process": "Not involved", + "Copy Link": { + "label": "Copy Link", + "url": "https://nwmd.social/s/copylink/test", + "title": "", }, - ], + }, + "message": "Already submitted", + "submission": { + "sid": [{"value": 123456}], + "uuid": [{"value": "test-value"}], + "revision_id": [{"value": 123456}], + "action_id": [ + { + "target_id": 1234, + "target_type": "node", + "target_uuid": "test-value", + "url": "/node/1234", + } + ], + "contact_id": [ + { + "target_id": 1234567, + "target_type": "contact", + "target_uuid": "test-value", + "url": "/contact/1234567", + } + ], + "status": [ + { + "target_id": 12, + "target_type": "test-value", + "target_uuid": "test-value", + "url": "/taxonomy/term/12", + } + ], + "testmode": [{"value": False}], + "edited": [{"value": True}], + "device": [], + "browser": [], + "browser_version": [], + "os": [], + "os_version": [], + "parent_url": [], + "source_code": [], + "search_value": [], + "created": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], + "changed": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], + "entitygroupfield": [ + { + "target_id": 12345678, + "target_type": "group_content", + "target_uuid": "test-value", + "url": "test-url", + } + ], + }, + "ref_id": "test-value", } - - tbl = Table( - [ - { - "id": "", - "user_id": "16778240", - "name": "Barack Obama", - "user_email": "", - "purchasing_time_frame": "Within a month", - "role_in_purchase_process": "Not involved", - }, - { - "id": "", - "user_id": "16779264", - "name": "", - "user_email": "", - "purchasing_time_frame": "Within a month", - "role_in_purchase_process": "Not involved", - }, - ] - ) - + tbl = Table([json_response]) m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - m.get(API_URL + "meetings/123/registrants", json=registrants) - assert_matching_tables(self.zoom.get_meeting_registrants(123), tbl) - - @requests_mock.Mocker() - def test_get_user_webinars(self, m): - webinars = { - "page_count": 1, - "page_size": 30, - "total_records": 4, - "next_page_token": "", - "webinars": [ - { - "uuid": "dsghfkhaewfds", - "id": "", - "host_id": "24654130000000", - "topic": "My Webinar", - "agenda": "Learn more about Zoom APIs", - "type": "5", - "duration": "60", - "start_time": "2019-09-24T22:00:00Z", - "timezone": "America/Los_Angeles", - "created_at": "2019-08-30T22:00:00Z", - "join_url": "https://zoom.us/0001000/awesomewebinar", - }, - { - "uuid": "dhf8as7dhf", - "id": "", - "host_id": "24654130000345", - "topic": "My Webinar", - "agenda": "Learn more about Zoom APIs", - "type": "5", - "duration": "60", - "start_time": "2019-09-24T22:00:00Z", - "timezone": "America/Los_Angeles", - "created_at": "2019-08-30T22:00:00Z", - "join_url": "https://zoom.us/0001000/awesomewebinar", - }, - ], - } - - tbl = Table( - [ - { - "uuid": "dsghfkhaewfds", - "id": "", - "host_id": "24654130000000", - "topic": "My Webinar", - "agenda": "Learn more about Zoom APIs", - "type": "5", - "duration": "60", - "start_time": "2019-09-24T22:00:00Z", - "timezone": "America/Los_Angeles", - "created_at": "2019-08-30T22:00:00Z", - "join_url": "https://zoom.us/0001000/awesomewebinar", - }, - { - "uuid": "dhf8as7dhf", - "id": "", - "host_id": "24654130000345", - "topic": "My Webinar", - "agenda": "Learn more about Zoom APIs", - "type": "5", - "duration": "60", - "start_time": "2019-09-24T22:00:00Z", - "timezone": "America/Los_Angeles", - "created_at": "2019-08-30T22:00:00Z", - "join_url": "https://zoom.us/0001000/awesomewebinar", - }, - ] + m.get( + f"{self.base_url}/campaign/{self.campaign_id}/form", + json=json_response, ) - - m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - m.get(API_URL + "users/123/webinars", json=webinars) - assert_matching_tables(self.zoom.get_user_webinars(123), tbl) + assert_matching_tables(self.nm.get_campaign(campaign_id=self.campaign_id), tbl) @requests_mock.Mocker() - def test_get_past_webinar_participants(self, m): - participants = { - "page_count": 1, - "page_size": 30, - "total_records": 4, - "next_page_token": "", - "participants": [ - { - "id": "", - "user_id": "sdfjkldsf87987", - "name": "Barack", - "user_email": "riya@sdfjkldsf87987.fdjfhdf", - "join_time": "2019-02-01T12:34:12.660Z", - "leave_time": "2019-03-01T12:34:12.660Z", - "duration": "20", - }, - { - "id": "", - "user_id": "sdfjkldsfdfgdfg", - "name": "Joe", - "user_email": "riya@sdfjkldsf87987.fdjfhdf", - "join_time": "2019-02-01T12:34:12.660Z", - "leave_time": "2019-03-01T12:34:12.660Z", - "duration": "20", + def test_get_campaigns(self, m): + lst = ["testCampaingID"] + json_response = { + "jsonapi": { + "version": "1.0", + "meta": {"links": {"self": {"href": "http://jsonapi.org/format/1.0/"}}}, + }, + "data": { + "type": "node--action", + "id": "testCampaingID", + "links": { + "self": { + "href": "https://base.newmode.net/jsonapi/node/action/testCampaingID?resourceVersion=id%test" + } }, - ], + "attributes": {}, + "relationships": {}, + }, } - - tbl = Table( - [ - { - "id": "", - "user_id": "sdfjkldsf87987", - "name": "Barack", - "user_email": "riya@sdfjkldsf87987.fdjfhdf", - "join_time": "2019-02-01T12:34:12.660Z", - "leave_time": "2019-03-01T12:34:12.660Z", - "duration": "20", - }, - { - "id": "", - "user_id": "sdfjkldsfdfgdfg", - "name": "Joe", - "user_email": "riya@sdfjkldsf87987.fdjfhdf", - "join_time": "2019-02-01T12:34:12.660Z", - "leave_time": "2019-03-01T12:34:12.660Z", - "duration": "20", - }, - ] - ) - m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - m.get(API_URL + "report/webinars/123/participants", json=participants) - assert_matching_tables(self.zoom.get_past_webinar_participants(123), tbl) + m.get(f"{API_CAMPAIGNS_URL}jsonapi/node/action", json=json_response) + assert_matching_tables(self.nm.get_campaigns(), lst) @requests_mock.Mocker() - def test_get_past_webinar_report(self, m): - report = { - "custom_keys": [{"key": "Host Nation", "value": "US"}], - "dept": "HR", - "duration": 2, - "end_time": "2022-03-15T07:42:22Z", - "id": 345678902224, - "participants_count": 4, - "start_time": "2022-03-15T07:40:46Z", - "topic": "My Meeting", - "total_minutes": 3, - "tracking_fields": [{"field": "Host Nation", "value": "US"}], - "type": 4, - "user_email": "jchill@example.com", - "user_name": "Jill Chill", - "uuid": "4444AAAiAAAAAiAiAiiAii==", + def test_get_recipient(self, m): + self.city = "Vancouver" + json_response = { + "subject": "test subject", + "message": "

Dear [send:full_name],
I know that you care about this example as much as I do.

\n

[contact:full_name]
[contact:email], [contact:full_address]

, subscriber_text_lb", + "id": "b3fc-xxxxxxxxxxxxxxxxxxxxxx-99a8", + "first_name": "Darcy", + "last_name": "Doogoode", + "full_name": "Darcy Doogoode", + "position": "MP", + "party": "Liberal", + "jurisdiction": "Vancouver East", + "rendered": "Darcy Doogoode (MP), Vancouver East, Liberal", } + tbl = Table([json_response]) m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - m.get(API_URL + "report/webinars/123", json=report) - assert_matching_tables(self.zoom.get_past_webinar_report(123), report) - - @requests_mock.Mocker() - def test_get_webinar_registrants(self, m): - registrants = { - "page_count": 1, - "page_size": 30, - "total_records": 4, - "next_page_token": "", - "registrants": [ - { - "id": "", - "email": "barack@obama.com", - "first_name": "Barack", - "last_name": "Obama", - "address": "dsfhkdjsfh st", - "city": "jackson heights", - "country": "US", - "zip": "11371", - "state": "NY", - "phone": "00000000", - "industry": "Food", - "org": "Cooking Org", - "job_title": "Chef", - "purchasing_time_frame": "1-3 months", - "role_in_purchase_process": "Influencer", - "no_of_employees": "10", - "comments": "Looking forward to the Webinar", - "custom_questions": [ - { - "title": "What do you hope to learn from this Webinar?", - "value": "Look forward to learning how you come up with recipes and services", # noqa: E501 - } - ], - "status": "approved", - "create_time": "2019-02-26T23:01:16.899Z", - "join_url": "https://zoom.us/webinar/mywebinariscool", - }, - { - "id": "", - "email": "joe@biden.com", - "first_name": "Joe", - "last_name": "Biden", - "address": "dsfhkdjsfh st", - "city": "jackson heights", - "country": "US", - "zip": "11371", - "state": "NY", - "phone": "00000000", - "industry": "Food", - "org": "Cooking Org", - "job_title": "Chef", - "purchasing_time_frame": "1-3 months", - "role_in_purchase_process": "Influencer", - "no_of_employees": "10", - "comments": "Looking forward to the Webinar", - "custom_questions": [ - { - "title": "What do you hope to learn from this Webinar?", - "value": "Look forward to learning how you come up with recipes and services", # noqa: E501 - } - ], - "status": "approved", - "create_time": "2019-02-26T23:01:16.899Z", - "join_url": "https://zoom.us/webinar/mywebinariscool", - }, - ], - } - - tbl = Table( - [ - { - "id": "", - "email": "barack@obama.com", - "first_name": "Barack", - "last_name": "Obama", - "address": "dsfhkdjsfh st", - "city": "jackson heights", - "country": "US", - "zip": "11371", - "state": "NY", - "phone": "00000000", - "industry": "Food", - "org": "Cooking Org", - "job_title": "Chef", - "purchasing_time_frame": "1-3 months", - "role_in_purchase_process": "Influencer", - "no_of_employees": "10", - "comments": "Looking forward to the Webinar", - "custom_questions": [ - { - "title": "What do you hope to learn from this Webinar?", - "value": "Look forward to learning how you come up with recipes and services", # noqa: E501 - } - ], - "status": "approved", - "create_time": "2019-02-26T23:01:16.899Z", - "join_url": "https://zoom.us/webinar/mywebinariscool", - }, - { - "id": "", - "email": "joe@biden.com", - "first_name": "Joe", - "last_name": "Biden", - "address": "dsfhkdjsfh st", - "city": "jackson heights", - "country": "US", - "zip": "11371", - "state": "NY", - "phone": "00000000", - "industry": "Food", - "org": "Cooking Org", - "job_title": "Chef", - "purchasing_time_frame": "1-3 months", - "role_in_purchase_process": "Influencer", - "no_of_employees": "10", - "comments": "Looking forward to the Webinar", - "custom_questions": [ - { - "title": "What do you hope to learn from this Webinar?", - "value": "Look forward to learning how you come up with recipes and services", # noqa: E501 - } - ], - "status": "approved", - "create_time": "2019-02-26T23:01:16.899Z", - "join_url": "https://zoom.us/webinar/mywebinariscool", - }, - ] + m.get(f"{self.base_url}/campaign/{self.campaign_id}/target", json=json_response) + assert_matching_tables( + self.nm.get_recipient(campaign_id=self.campaign_id, city=self.city), tbl ) - m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - m.get(API_URL + "webinars/123/registrants", json=registrants) - assert_matching_tables(self.zoom.get_webinar_registrants(123), tbl) - @requests_mock.Mocker() - def test_get_meeting_poll_metadata(self, m): - poll = { - "id": 1, - "status": "started", - "anonymous": "False", - "poll_type": 1, - "questions": [ - { - "answer_max_character": 1, - "answer_min_character": 1, - "answer_required": "False", - "answers": "Larry David's Curb Your Enthusiasm", - "case_sensitive": "True", - "name": "Secret Truth", - "prompts": [ - { - "prompt_question": "What's the secret truth of the universe?", - "prompt_right_answers": [ - "Pizza delivery", - "Larry David's Curb Your Enthusiasm", - ], - } - ], - "rating_max_label": "", - "rating_max_value": 1, - "rating_min_label": "", - "rating_min_value": 0, - "right_answers": "", - "show_as_dropdown": False, - "type": "short_answer", - } - ], - } - - tbl = Table( - [ - { - "answer_max_character": 1, - "answer_min_character": 1, - "answer_required": "False", - "answers": "Larry David's Curb Your Enthusiasm", - "case_sensitive": "True", - "name": "Secret Truth", - "rating_max_label": "", - "rating_max_value": 1, - "rating_min_label": "", - "rating_min_value": 0, - "right_answers": "", - "show_as_dropdown": False, - "type": "short_answer", - "prompts__prompt_question": "What's the secret truth of the universe?", - "prompts__prompt_right_answers": [ - "Pizza delivery", - "Larry David's Curb Your Enthusiasm", - ], - } - ], - ) - - m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - m.get(API_URL + "meetings/123/polls/1", json=poll) - assert_matching_tables(self.zoom.get_meeting_poll_metadata(123, 1), tbl) - - @requests_mock.Mocker() - def test_get_meeting_all_polls_metadata(self, m): - polls = { - "polls": [ - { - "id": 1, - "status": "started", - "anonymous": "False", - "poll_type": 1, - "questions": [ - { - "answer_max_character": 1, - "answer_min_character": 1, - "answer_required": "False", - "answers": "Larry David's Curb Your Enthusiasm", - "case_sensitive": "True", - "name": "Secret Truth", - "prompts": [ - { - "prompt_question": "What's the secret truth of the universe?", - "prompt_right_answers": [ - "Pizza delivery", - "Larry David's Curb Your Enthusiasm", - ], - } - ], - "rating_max_label": "", - "rating_max_value": 1, - "rating_min_label": "", - "rating_min_value": 0, - "right_answers": "", - "show_as_dropdown": False, - "type": "short_answer", - } - ], + def test_run_submit(self, m): + json_response = { + "contact": { + "id": [{"value": 1883}], + "uuid": [{"value": "2efe-xxxxxxxxxxxxxxxxxxxxxx-2044"}], + "revision_id": [{"value": 1954}], + "org_id": [{"value": 1}], + "honorific": [], + "first_name": [{"value": "Sammy"}], + "last_name": [{"value": "Supporter"}], + "name_suffix": [], + "email": [{"value": "test_abc@test.com"}], + "mobile_phone": [], + "alternate_phone": [], + "twitter_handle": [], + "street_address": [{"value": "312 Main Street"}], + "city": [{"value": "Vancouver"}], + "region": [{"value": "BC"}], + "country": [{"value": "CA"}], + "postal_code": [{"value": "V6A 2T2"}], + "latitude": [{"value": 49.282039}], + "longitude": [{"value": -123.099221}], + "opt_in": [{"value": True}], + "nm_product_opt_in": [{"value": True}], + "nm_marketing_opt_in": [{"value": True}], + "groups": [ + { + "target_id": 58, + "target_type": "contact_group", + "target_uuid": "f426-xxxxxxxxxxxxxxxxxxxxxx-6712", + "url": "/contact-group/58", + } + ], + "created": [{"value": "1730818224", "format": "Y-m-d\\TH:i:sP"}], + "changed": [{"value": "1730818779", "format": "Y-m-d\\TH:i:sP"}], + "prefill_hash": [ + { + "value": "706a1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxe501" + } + ], + "subscriber": [], + "sync_status": [[]], + "entitygroupfield": [ + { + "target_id": 5648, + "target_type": "group_content", + "target_uuid": "68be-xxxxxxxxxxxxxxxxxxxxxx-095c", + "url": "/group/1/content/5648", + } + ], + }, + "submission": { + "sid": [{"value": 692}], + "uuid": [{"value": "364a-xxxxxxxxxxxxxxxxxxxxxx-d545"}], + "revision_id": [{"value": 692}], + "action_id": [ + { + "target_id": 197, + "target_type": "node", + "target_uuid": "54f7-xxxxxxxxxxxxxxxxxxxxxx-b11f", + "url": "/node/197", + } + ], + "contact_id": [ + { + "target_id": 1883, + "target_type": "contact", + "target_uuid": "2efe-xxxxxxxxxxxxxxxxxxxxxx-2044", + "url": "/contact/1883", + } + ], + "status": [ + { + "target_id": 78, + "target_type": "taxonomy_term", + "target_uuid": "1sb6-xxxxxxxxxxxxxxxxxxxxxx-ba19", + "url": "/taxonomy/term/78", + } + ], + "testmode": [{"value": False}], + "edited": [{"value": False}], + "device": [{"value": "PC"}], + "browser": [{"value": "Firefox"}], + "browser_version": [{"value": "132.0"}], + "os": [{"value": "GNU/Linux"}], + "os_version": [], + "parent_url": [{"value": "https://www.mysite.com/mycampaign"}], + "source_code": [{"value": "facebook"}], + "search_value": [], + "created": [{"value": "1730818779", "format": "Y-m-d\\TH:i:sP"}], + "changed": [{"value": "1730818779", "format": "Y-m-d\\TH:i:sP"}], + "entitygroupfield": [ + { + "target_id": 5652, + "target_type": "group_content", + "target_uuid": "2119-xxxxxxxxxxxxxxxxxxxxxx-ce92", + "url": "/group/1/content/5xx2", + } + ], + }, + "links": { + "Facebook": { + "label": "Share on Facebook", + "url": "https://www.facebook.com/sharer.php?s=100&u=https://www.mysite.com/mycampaign", + "title": "", }, - { - "id": 2, - "status": "started", - "anonymous": "False", - "poll_type": 1, - "questions": [ - { - "answer_max_character": 1, - "answer_min_character": 1, - "answer_required": "False", - "answers": "Mets", - "case_sensitive": "True", - "name": "Play ball", - "prompts": [ - { - "prompt_question": "Best NY baseball team?", - "prompt_right_answers": ["Mets"], - } - ], - "rating_max_label": "", - "rating_max_value": 1, - "rating_min_label": "", - "rating_min_value": 0, - "right_answers": "", - "show_as_dropdown": True, - "type": "short_answer", - } - ], + "Twitter": { + "label": "Tweet to your followers", + "url": "http://base.test:8020/s/twitter/9VI1xxxxxxxxxg=/b", + "title": "", }, - ] - } - - tbl = Table( - [ - { - "id": 1, - "status": "started", - "anonymous": "False", - "poll_type": 1, - "questions__answer_max_character": 1, - "questions__answer_min_character": 1, - "questions__answer_required": "False", - "questions__answers": "Larry David's Curb Your Enthusiasm", - "questions__case_sensitive": "True", - "questions__name": "Secret Truth", - "questions__prompts": [ - { - "prompt_question": "What's the secret truth of the universe?", - "prompt_right_answers": [ - "Pizza delivery", - "Larry David's Curb Your Enthusiasm", - ], - } - ], - "questions__rating_max_label": "", - "questions__rating_max_value": 1, - "questions__rating_min_label": "", - "questions__rating_min_value": 0, - "questions__right_answers": "", - "questions__show_as_dropdown": False, - "questions__type": "short_answer", + "Email": { + "label": "Send an email", + "url": "http://base.test:8020/s/email/9VI1xxxxxxxxxg=/b", + "title": "Add your voice to this campaign!", }, - { - "id": 2, - "status": "started", - "anonymous": "False", - "poll_type": 1, - "questions__answer_max_character": 1, - "questions__answer_min_character": 1, - "questions__answer_required": "False", - "questions__answers": "Mets", - "questions__case_sensitive": "True", - "questions__name": "Play ball", - "questions__prompts": [ - { - "prompt_question": "Best NY baseball team?", - "prompt_right_answers": ["Mets"], - } - ], - "questions__rating_max_label": "", - "questions__rating_max_value": 1, - "questions__rating_min_label": "", - "questions__rating_min_value": 0, - "questions__right_answers": "", - "questions__show_as_dropdown": True, - "questions__type": "short_answer", + "Copy Link": { + "label": "Copy Link", + "url": "http://base.test:8020/s/copylink/9VI1MbcwMCg=/b", + "title": "", }, - ] - ) - - m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - m.get(API_URL + "meetings/123/polls", json=polls) - assert_matching_tables(self.zoom.get_meeting_all_polls_metadata(123), tbl) - - @requests_mock.Mocker() - def test_get_past_meeting_poll_metadata(self, m): - poll = { - "id": 1, - "status": "started", - "anonymous": "False", - "poll_type": 1, - "questions": [ - { - "answer_max_character": 1, - "answer_min_character": 1, - "answer_required": "False", - "answers": "Larry David's Curb Your Enthusiasm", - "case_sensitive": "True", - "name": "Secret Truth", - "prompts": [ - { - "prompt_question": "What's the secret truth of the universe?", - "prompt_right_answers": [ - "Pizza delivery", - "Larry David's Curb Your Enthusiasm", - ], - } - ], - "rating_max_label": "", - "rating_max_value": 1, - "rating_min_label": "", - "rating_min_value": 0, - "right_answers": "", - "show_as_dropdown": False, - "type": "short_answer", - } - ], - } - - tbl = Table( - [ - { - "answer_max_character": 1, - "answer_min_character": 1, - "answer_required": "False", - "answers": "Larry David's Curb Your Enthusiasm", - "case_sensitive": "True", - "name": "Secret Truth", - "rating_max_label": "", - "rating_max_value": 1, - "rating_min_label": "", - "rating_min_value": 0, - "right_answers": "", - "show_as_dropdown": False, - "type": "short_answer", - "prompts__prompt_question": "What's the secret truth of the universe?", - "prompts__prompt_right_answers": [ - "Pizza delivery", - "Larry David's Curb Your Enthusiasm", - ], - } - ] - ) - - m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - m.get(API_URL + "past_meetings/123/polls", json=poll) - assert_matching_tables(self.zoom.get_past_meeting_poll_metadata(123), tbl) - - @requests_mock.Mocker() - def test_get_webinar_poll_metadata(self, m): - poll = { - "id": "QalIoKWLTJehBJ8e1xRrbQ", - "status": "notstart", - "anonymous": True, - "poll_type": 2, - "questions": [ - { - "answer_max_character": 200, - "answer_min_character": 1, - "answer_required": False, - "answers": ["Extremely useful"], - "case_sensitive": False, - "name": "How useful was this meeting?", - "prompts": [ - { - "prompt_question": "How are you?", - "prompt_right_answers": ["Good"], - } - ], - "rating_max_label": "Extremely Likely", - "rating_max_value": 4, - "rating_min_label": "Not likely", - "rating_min_value": 0, - "right_answers": ["Good"], - "show_as_dropdown": False, - "type": "single", - } - ], - "title": "Learn something new", - } - - tbl = Table( - [ - { - "answer_max_character": 200, - "answer_min_character": 1, - "answer_required": False, - "answers": ["Extremely useful"], - "case_sensitive": False, - "name": "How useful was this meeting?", - "rating_max_label": "Extremely Likely", - "rating_max_value": 4, - "rating_min_label": "Not likely", - "rating_min_value": 0, - "right_answers": ["Good"], - "show_as_dropdown": False, - "type": "single", - "prompts__prompt_question": "How are you?", - "prompts__prompt_right_answers": ["Good"], - } - ] - ) - - m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - m.get(API_URL + "webinars/123/polls/456", json=poll) - assert_matching_tables(self.zoom.get_webinar_poll_metadata(123, 456), tbl) - - @requests_mock.Mocker() - def test_get_webinar_all_polls_metadata(self, m): - polls = { - "polls": [ - { - "id": "QalIoKWLTJehBJ8e1xRrbQ", - "status": "notstart", - "anonymous": True, - "poll_type": 2, - "questions": [ - { - "answer_max_character": 200, - "answer_min_character": 1, - "answer_required": False, - "answers": ["Extremely useful"], - "case_sensitive": False, - "name": "How useful was this meeting?", - "prompts": [ - { - "prompt_question": "How are you?", - "prompt_right_answers": ["Good"], - } - ], - "rating_max_label": "Extremely Likely", - "rating_max_value": 4, - "rating_min_label": "Not likely", - "rating_min_value": 0, - "right_answers": ["Good"], - "show_as_dropdown": False, - "type": "single", - } - ], - "title": "Learn something new", - } - ], - "total_records": 1, + }, + "queue_id": "3xx6", + "ref_id": "706axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxe501", } - - tbl = Table( - [ - { - "id": "QalIoKWLTJehBJ8e1xRrbQ", - "status": "notstart", - "anonymous": True, - "poll_type": 2, - "title": "Learn something new", - "questions__answer_max_character": 200, - "questions__answer_min_character": 1, - "questions__answer_required": False, - "questions__answers": ["Extremely useful"], - "questions__case_sensitive": False, - "questions__name": "How useful was this meeting?", - "questions__prompts": [ - { - "prompt_question": "How are you?", - "prompt_right_answers": ["Good"], - } - ], - "questions__rating_max_label": "Extremely Likely", - "questions__rating_max_value": 4, - "questions__rating_min_label": "Not likely", - "questions__rating_min_value": 0, - "questions__right_answers": ["Good"], - "questions__show_as_dropdown": False, - "questions__type": "single", - } - ] - ) - - m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - m.get(API_URL + "webinars/123/polls", json=polls) - assert_matching_tables(self.zoom.get_webinar_all_polls_metadata(123), tbl) - - @requests_mock.Mocker() - def test_get_past_webinar_poll_metadata(self, m): - poll = { - "id": "QalIoKWLTJehBJ8e1xRrbQ", - "status": "notstart", - "anonymous": True, - "poll_type": 2, - "questions": [ - { - "answer_max_character": 200, - "answer_min_character": 1, - "answer_required": False, - "answers": ["Extremely useful"], - "case_sensitive": False, - "name": "How useful was this meeting?", - "prompts": [ - { - "prompt_question": "How are you?", - "prompt_right_answers": ["Good"], - } - ], - "rating_max_label": "Extremely Likely", - "rating_max_value": 4, - "rating_min_label": "Not likely", - "rating_min_value": 0, - "right_answers": ["Good"], - "show_as_dropdown": False, - "type": "single", - } - ], - "title": "Learn something new", + json_response = { + "action_id": self.campaign_id, + "first_name": "TestFirstName", + "last_name": "TestLastName", + "email": "test_abc@test.com", + "opt_in": 1, + "address": {"postal_code": "V6A 2T2"}, + "subject": "This is my subject", + "message": "This is my letter", } - tbl = Table( - [ - { - "answer_max_character": 200, - "answer_min_character": 1, - "answer_required": False, - "answers": ["Extremely useful"], - "case_sensitive": False, - "name": "How useful was this meeting?", - "rating_max_label": "Extremely Likely", - "rating_max_value": 4, - "rating_min_label": "Not likely", - "rating_min_value": 0, - "right_answers": ["Good"], - "show_as_dropdown": False, - "type": "single", - "prompts__prompt_question": "How are you?", - "prompts__prompt_right_answers": ["Good"], - } - ], - ) - m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - m.get(API_URL + "past_webinars/123/polls", json=poll) - assert_matching_tables(self.zoom.get_past_webinar_poll_metadata(123), tbl) - - @requests_mock.Mocker() - def test_get_meeting_poll_results(self, m): - poll = { - "id": 123456, - "uuid": "4444AAAiAAAAAiAiAiiAii==", - "start_time": "2022-02-01T12:34:12.660Z", - "questions": [ - { - "email": "jchill@example.com", - "name": "Jill Chill", - "first_name": "Jill", - "last_name": "Chill", - "question_details": [ - { - "answer": "I am wonderful.", - "date_time": "2022-02-01T12:37:12.660Z", - "polling_id": "798fGJEWrA", - "question": "How are you?", - } - ], - } - ], - } - - tbl = Table( - [ - { - "email": "jchill@example.com", - "name": "Jill Chill", - "first_name": "Jill", - "last_name": "Chill", - "answer": "I am wonderful.", - "date_time": "2022-02-01T12:37:12.660Z", - "polling_id": "798fGJEWrA", - "question": "How are you?", - } - ] + m.post( + f"{self.base_url}/campaign/{self.campaign_id}/submit", + json=json_response, ) - - m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - m.get(API_URL + "report/meetings/123/polls", json=poll) - assert_matching_tables(self.zoom.get_meeting_poll_results(123), tbl) - - @requests_mock.Mocker() - def test_get_webinar_poll_results(self, m): - poll = { - "id": 123456, - "questions": [ - { - "email": "jchill@example.com", - "name": "Jill Chill", - "first_name": "Jill", - "last_name": "Chill", - "question_details": [ - { - "answer": "I am wonderful.", - "date_time": "2022-02-01T12:37:12.660Z", - "polling_id": "798fGJEWrA", - "question": "How are you?", - } - ], - } - ], - "start_time": "2022-02-01T12:34:12.660Z", - "uuid": "4444AAAiAAAAAiAiAiiAii==", - } - - tbl = Table( - [ - { - "email": "jchill@example.com", - "name": "Jill Chill", - "first_name": "Jill", - "last_name": "Chill", - "answer": "I am wonderful.", - "date_time": "2022-02-01T12:37:12.660Z", - "polling_id": "798fGJEWrA", - "question": "How are you?", - } - ] + assert_matching_tables( + self.nm.run_submit(campaign_id=self.campaign_id, json=json_response), + json_response, ) - - m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - m.get(API_URL + "report/webinars/123/polls", json=poll) - assert_matching_tables(self.zoom.get_webinar_poll_results(123), tbl) From 2e15310487b2a1dcb4e161b467a279b7483d716c Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Fri, 20 Dec 2024 13:51:31 -0800 Subject: [PATCH 05/30] ruff format --- parsons/newmode/newmode_v2.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/parsons/newmode/newmode_v2.py b/parsons/newmode/newmode_v2.py index 17aa64fb46..3627cd0a77 100644 --- a/parsons/newmode/newmode_v2.py +++ b/parsons/newmode/newmode_v2.py @@ -11,7 +11,6 @@ class NewmodeV2(object): - def __init__( self, client_id=None, @@ -35,9 +34,7 @@ def __init__( self.api_version = check_env.check("NEWMODE_API_VERSION", api_version) self.base_url = API_URL self.client_id = check_env.check("NEWMODE_API_CLIENT_ID", client_id) - self.__client_secret = check_env.check( - "NEWMODE_API_CLIENT_SECRET", client_secret - ) + self.__client_secret = check_env.check("NEWMODE_API_CLIENT_SECRET", client_secret) self.headers = {"content-type": "application/json"} def convert_to_table(self, data): @@ -184,16 +181,10 @@ def get_recipient( "region": region, } if all(x is None for x in address_params.values()): - logger.error( - "Please specify a street address, city, postal code, and/or region." - ) + logger.error("Please specify a street address, city, postal code, and/or region.") raise Exception("Incomplete Request") - params = { - f"address[value][{key}]": value - for key, value in address_params.items() - if value - } + params = {f"address[value][{key}]": value for key, value in address_params.items() if value} response = self.converted_request( endpoint=f"campaign/{campaign_id}/target", method="GET", From eb89e070e28b2490af5b8ce34f032f8d09dbff12 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Fri, 20 Dec 2024 15:53:44 -0800 Subject: [PATCH 06/30] clean up --- parsons/newmode/newmode_v2.py | 1 - 1 file changed, 1 deletion(-) diff --git a/parsons/newmode/newmode_v2.py b/parsons/newmode/newmode_v2.py index 3627cd0a77..86fa3e0194 100644 --- a/parsons/newmode/newmode_v2.py +++ b/parsons/newmode/newmode_v2.py @@ -53,7 +53,6 @@ def base_request(self, method, url, data=None, json=None, data_key=None, params= url=url, req_type=method, json=json, data=data, params=params ) response.raise_for_status() - print("URL:", response.request.url) success_codes = [200, 201, 202, 204] self.client.validate_response(response) if response.status_code in success_codes: From e9bbadc5fa547cfe3c45bd0423a4c088d5ac2c38 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Sun, 5 Jan 2025 19:08:06 -0800 Subject: [PATCH 07/30] collapse classes and add back submissions --- parsons/__init__.py | 1 - parsons/newmode/__init__.py | 3 +- parsons/newmode/newmode.py | 252 ++++++++++++++++++- parsons/newmode/newmode_v2.py | 237 ------------------ test/test_newmode/test_newmode.py | 350 ++++++++++++++++++++++++++- test/test_newmode/test_newmode_v2.py | 347 -------------------------- 6 files changed, 595 insertions(+), 595 deletions(-) delete mode 100644 parsons/newmode/newmode_v2.py delete mode 100644 test/test_newmode/test_newmode_v2.py diff --git a/parsons/__init__.py b/parsons/__init__.py index 9f034230a4..227aac8fea 100644 --- a/parsons/__init__.py +++ b/parsons/__init__.py @@ -73,7 +73,6 @@ ("parsons.mobilize_america.ma", "MobilizeAmerica"), ("parsons.nation_builder.nation_builder", "NationBuilder"), ("parsons.newmode.newmode", "Newmode"), - ("parsons.newmode.newmode_v2", "NewmodeV2"), ("parsons.ngpvan.van", "VAN"), ("parsons.notifications.gmail", "Gmail"), ("parsons.notifications.slack", "Slack"), diff --git a/parsons/newmode/__init__.py b/parsons/newmode/__init__.py index b753baba12..161b0a1cc6 100644 --- a/parsons/newmode/__init__.py +++ b/parsons/newmode/__init__.py @@ -1,4 +1,3 @@ from parsons.newmode.newmode import Newmode -from parsons.newmode.newmode_v2 import NewmodeV2 -__all__ = ["Newmode", "NewmodeV2"] +__all__ = ["Newmode"] diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index 7317e1e70a..ee2bd260d6 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -1,12 +1,17 @@ from Newmode import Client +from parsons.utilities.oauth_api_connector import OAuth2APIConnector from parsons.utilities import check_env from parsons.etl import Table import logging logger = logging.getLogger(__name__) +V2_API_URL = "https://base.newmode.net/api/" +V2_API_AUTH_URL = "https://base.newmode.net/oauth/token/" +V2_API_CAMPAIGNS_URL = "https://base.newmode.net/" -class Newmode: + +class NewmodeV1: def __init__(self, api_user=None, api_password=None, api_version=None): """ Args: @@ -25,10 +30,6 @@ def __init__(self, api_user=None, api_password=None, api_version=None): logger.warning("Newmode V1 API will be sunset in Feburary 2025.") self.api_user = check_env.check("NEWMODE_API_USER", api_user) self.api_password = check_env.check("NEWMODE_API_PASSWORD", api_password) - - if api_version is None: - api_version = "v1.0" - self.api_version = check_env.check("NEWMODE_API_VERSION", api_version) self.client = Client(api_user, api_password, api_version) @@ -324,3 +325,244 @@ def get_outreach(self, outreach_id, params={}): else: logging.warning("Empty outreach returned") return None + + +class NewmodeV2: + def __init__( + self, + client_id=None, + client_secret=None, + api_version="v2.1", + ): + """ + Instantiate Class + `Args`: + api_user: str + The username to use for the API requests. Not required if ``NEWMODE_V2_API_URL`` + env variable set. + api_password: str + The password to use for the API requests. Not required if ``NEWMODE_API_PASSWORD`` + env variable set. + api_version: str + The api version to use. Defaults to v1.0 + Returns: + NewMode Class + """ + self.api_version = check_env.check("NEWMODE_API_VERSION", api_version) + self.base_url = V2_API_URL + self.client_id = check_env.check("NEWMODE_API_CLIENT_ID", client_id) + self.__client_secret = check_env.check("NEWMODE_API_CLIENT_SECRET", client_secret) + self.headers = {"content-type": "application/json"} + + def convert_to_table(self, data): + """Internal method to create a Parsons table from a data element.""" + table = None + if type(data) is list: + table = Table(data) + else: + table = Table([data]) + + return table + + def base_request(self, method, url, data=None, json=None, data_key=None, params={}): + response = None + response = self.client.request( + url=url, req_type=method, json=json, data=data, params=params + ) + response.raise_for_status() + success_codes = [200, 201, 202, 204] + self.client.validate_response(response) + if response.status_code in success_codes: + if self.client.json_check(response): + response_json = response.json() + return response_json[data_key] if data_key else response_json + else: + return response.status_code + return response + + def converted_request( + self, + endpoint, + method, + supports_version=True, + data=None, + json=None, + params={}, + convert_to_table=True, + data_key=None, + ): + self.client = OAuth2APIConnector( + uri=self.base_url, + auto_refresh_url=V2_API_AUTH_URL, + client_id=self.client_id, + client_secret=self.__client_secret, + headers=self.headers, + token_url=V2_API_AUTH_URL, + grant_type="client_credentials", + ) + + url = f"{self.api_version}/{endpoint}" if supports_version else endpoint + response = self.base_request( + method=method, + url=url, + json=json, + data=data, + params=params, + data_key=data_key, + ) + if not response: + logging.warning(f"Empty result returned from endpoint: {endpoint}") + if convert_to_table: + return self.convert_to_table(response) + else: + return response + + def get_campaign(self, campaign_id, params={}): + """ + Retrieve a specific campaign by ID. + + In v2, a campaign is equivalent to Tools or Actions in V1. + `Args:` + campaign_id: str + The ID of the campaign to retrieve. + params: dict + Query parameters to include in the request. + `Returns:` + Parsons Table containing campaign data. + """ + endpoint = f"/campaign/{campaign_id}/form" + response = self.converted_request( + endpoint=endpoint, + method="GET", + params=params, + ) + return response + + def get_campaign_ids(self, params={}): + """ + Retrieve all campaigns + In v2, a campaign is equivalent to Tools or Actions in V1. + `Args:` + organization_id: str + ID of organization + params: dict + Query parameters to include in the request. + `Returns:` + List containing all campaign ids. + """ + self.base_url = V2_API_CAMPAIGNS_URL + self.api_version = "jsonapi" + self.headers = { + "content-type": "application/vnd.api+json", + "accept": "application/vnd.api+json", + "authorization": "Bearer 1234567890", + } + endpoint = "node/action" + response = self.converted_request( + endpoint=endpoint, method="GET", params=params, data_key="data" + ) + return response["id"] + + def get_recipient( + self, + campaign_id, + street_address=None, + city=None, + postal_code=None, + region=None, + params={}, + ): + """ + Retrieve a specific recipient by ID + `Args:` + campaign_id: str + The ID of the campaign to retrieve. + street_address: str + Street address of recipient + city: str + City of recipient + postal_code: str + Postal code of recipient + region: str + Region (i.e. state/province abbreviation) of recipient + params: dict + Query parameters to include in the request. + `Returns:` + Parsons Table containing recipient data. + """ + address_params = { + "street_address": street_address, + "city": city, + "postal_code": postal_code, + "region": region, + } + if all(x is None for x in address_params.values()): + logger.error("Please specify a street address, city, postal code, and/or region.") + raise Exception("Incomplete Request") + + params = {f"address[value][{key}]": value for key, value in address_params.items() if value} + response = self.converted_request( + endpoint=f"campaign/{campaign_id}/target", + method="GET", + params=params, + ) + return response + + def run_submit(self, campaign_id, json=None, data=None, params={}): + """ + Pass a submission from a supporter to a campaign + that ultimately fills in a petition, + sends an email or triggers a phone call + depending on your campaign type + + `Args:` + campaign_id: str + The ID of the campaign to retrieve. + params: dict + Query parameters to include in the request. + `Returns:` + Parsons Table containing submit data. + """ + + response = self.converted_request( + endpoint=f"campaign/{campaign_id}/submit", + method="POST", + data=data, + json=json, + params=params, + convert_to_table=False, + ) + return response + + def get_submissions(self, campaign_id, params={}): + """ + Retrieve and sort submissions and contact data + for a specified campaign using a range of filters + that include campaign id, data range and submission status + + `Args:` + params: dict + Query parameters to include in the request. + `Returns:` + Parsons Table containing submit data. + """ + params = {"action": campaign_id} + response = self.converted_request(endpoint="submission", method="GET", params=params) + return response + + +class Newmode: + def __new__( + cls, + client_id=None, + client_secret=None, + api_user=None, + api_password=None, + api_version="v1.0", + ): + if "v2" in api_version: + return NewmodeV2( + client_id=client_id, client_secret=client_secret, api_version=api_version + ) + else: + return NewmodeV1(api_user=api_user, api_password=api_password, api_version=api_version) diff --git a/parsons/newmode/newmode_v2.py b/parsons/newmode/newmode_v2.py deleted file mode 100644 index 86fa3e0194..0000000000 --- a/parsons/newmode/newmode_v2.py +++ /dev/null @@ -1,237 +0,0 @@ -from parsons.utilities.oauth_api_connector import OAuth2APIConnector -from parsons.utilities import check_env -from parsons import Table -import logging - -logger = logging.getLogger(__name__) - -API_URL = "https://base.newmode.net/api/" -API_AUTH_URL = "https://base.newmode.net/oauth/token/" -API_CAMPAIGNS_URL = "https://base.newmode.net/" - - -class NewmodeV2(object): - def __init__( - self, - client_id=None, - client_secret=None, - api_version="v2.1", - ): - """ - Instantiate Class - `Args`: - api_user: str - The username to use for the API requests. Not required if ``NEWMODE_API_URL`` - env variable set. - api_password: str - The password to use for the API requests. Not required if ``NEWMODE_API_PASSWORD`` - env variable set. - api_version: str - The api version to use. Defaults to v1.0 - Returns: - NewMode Class - """ - self.api_version = check_env.check("NEWMODE_API_VERSION", api_version) - self.base_url = API_URL - self.client_id = check_env.check("NEWMODE_API_CLIENT_ID", client_id) - self.__client_secret = check_env.check("NEWMODE_API_CLIENT_SECRET", client_secret) - self.headers = {"content-type": "application/json"} - - def convert_to_table(self, data): - """Internal method to create a Parsons table from a data element.""" - table = None - if type(data) is list: - table = Table(data) - else: - table = Table([data]) - - return table - - def base_request(self, method, url, data=None, json=None, data_key=None, params={}): - response = None - response = self.client.request( - url=url, req_type=method, json=json, data=data, params=params - ) - response.raise_for_status() - success_codes = [200, 201, 202, 204] - self.client.validate_response(response) - if response.status_code in success_codes: - if self.client.json_check(response): - response_json = response.json() - return response_json[data_key] if data_key else response_json - else: - return response.status_code - return response - - def converted_request( - self, - endpoint, - method, - supports_version=True, - data=None, - json=None, - params={}, - convert_to_table=True, - data_key=None, - ): - self.client = OAuth2APIConnector( - uri=self.base_url, - auto_refresh_url=API_AUTH_URL, - client_id=self.client_id, - client_secret=self.__client_secret, - headers=self.headers, - token_url=API_AUTH_URL, - grant_type="client_credentials", - ) - - url = f"{self.api_version}/{endpoint}" if supports_version else endpoint - response = self.base_request( - method=method, - url=url, - json=json, - data=data, - params=params, - data_key=data_key, - ) - if not response: - logging.warning(f"Empty result returned from endpoint: {endpoint}") - if convert_to_table: - return self.convert_to_table(response) - else: - return response - - def get_campaign(self, campaign_id, params={}): - """ - Retrieve a specific campaign by ID. - - In v2, a campaign is equivalent to Tools or Actions in V1. - `Args:` - campaign_id: str - The ID of the campaign to retrieve. - params: dict - Query parameters to include in the request. - `Returns:` - Parsons Table containing campaign data. - """ - endpoint = f"/campaign/{campaign_id}/form" - response = self.converted_request( - endpoint=endpoint, - method="GET", - params=params, - ) - return response - - def get_campaigns(self, params={}): - """ - Retrieve all campaigns - In v2, a campaign is equivalent to Tools or Actions in V1. - `Args:` - organization_id: str - ID of organization - params: dict - Query parameters to include in the request. - `Returns:` - List containing all campaign ids. - """ - self.base_url = API_CAMPAIGNS_URL - self.api_version = "jsonapi" - self.headers = { - "content-type": "application/vnd.api+json", - "accept": "application/vnd.api+json", - "authorization": "Bearer 1234567890", - } - endpoint = "node/action" - response = self.converted_request( - endpoint=endpoint, method="GET", params=params, data_key="data" - ) - return response["id"] - - def get_recipient( - self, - campaign_id, - street_address=None, - city=None, - postal_code=None, - region=None, - params={}, - ): - """ - Retrieve a specific recipient by ID - `Args:` - campaign_id: str - The ID of the campaign to retrieve. - street_address: str - Street address of recipient - city: str - City of recipient - postal_code: str - Postal code of recipient - region: str - Region (i.e. state/province abbreviation) of recipient - params: dict - Query parameters to include in the request. - `Returns:` - Parsons Table containing recipient data. - """ - address_params = { - "street_address": street_address, - "city": city, - "postal_code": postal_code, - "region": region, - } - if all(x is None for x in address_params.values()): - logger.error("Please specify a street address, city, postal code, and/or region.") - raise Exception("Incomplete Request") - - params = {f"address[value][{key}]": value for key, value in address_params.items() if value} - response = self.converted_request( - endpoint=f"campaign/{campaign_id}/target", - method="GET", - params=params, - ) - return response - - def run_submit(self, campaign_id, json=None, data=None, params={}): - """ - Pass a submission from a supporter to a campaign - that ultimately fills in a petition, - sends an email or triggers a phone call - depending on your campaign type - - `Args:` - campaign_id: str - The ID of the campaign to retrieve. - params: dict - Query parameters to include in the request. - `Returns:` - Parsons Table containing submit data. - """ - - response = self.converted_request( - endpoint=f"campaign/{campaign_id}/submit", - method="POST", - data=data, - json=json, - params=params, - convert_to_table=False, - ) - return response - - # TODO: add get_submissions method after updates from NewMode - # def get_submissions(self, campaign_id, params={}): - # """ - # Retrieve and sort submission and contact data - # for your organization using a range of filters - # that include campaign id, data range and submission status - - # `Args:` - # params: dict - # Query parameters to include in the request. - # `Returns:` - # Parsons Table containing submit data. - # """ - # params = {"campaign": campaign_id} - # response = self.converted_request( - # endpoint="submission", method="GET", params=params - # ) - # return response diff --git a/test/test_newmode/test_newmode.py b/test/test_newmode/test_newmode.py index 294e8ca478..71f5c21136 100644 --- a/test/test_newmode/test_newmode.py +++ b/test/test_newmode/test_newmode.py @@ -1,15 +1,25 @@ import os import unittest import unittest.mock as mock -from parsons import Newmode +import requests_mock +from parsons import NewmodeV1, NewmodeV2 +from test.utils import assert_matching_tables +CLIENT_ID = "fakeClientID" +CLIENT_SECRET = "fakeClientSecret" -class TestNewmode(unittest.TestCase): + +V2_API_URL = "https://base.newmode.net/api/" +V2_API_AUTH_URL = "https://base.newmode.net/oauth/token/" +V2_API_CAMPAIGNS_URL = "https://base.newmode.net/" + + +class TestNewmodeV1(unittest.TestCase): def setUp(self): os.environ["NEWMODE_API_USER"] = "MYFAKEUSERNAME" os.environ["NEWMODE_API_PASSWORD"] = "MYFAKEPASSWORD" - self.nm = Newmode() + self.nm = NewmodeV1() self.nm.client = mock.MagicMock() self.nm.client.getTools.return_value = [ @@ -158,3 +168,337 @@ def test_get_outreach(self): response = self.nm.get_outreach(id) self.nm.client.getOutreach.assert_called_with(id, params={}) self.assertEqual(response["name"], "Outreach 1") + + +class TestNewmodeV2(unittest.TestCase): + @requests_mock.Mocker() + def setUp(self, m): + m.post(V2_API_AUTH_URL, json={"access_token": "fakeAccessToken"}) + self.nm = NewmodeV2(CLIENT_ID, CLIENT_SECRET) + self.campaign_id = "fakeCampaignID" + self.api_version = "v2.1" + self.base_url = f"{V2_API_URL}{self.api_version}" + + @requests_mock.Mocker() + def test_get_campaign(self, m): + json_response = { + "contact": { + "id": [{"value": 12345678}], + "uuid": [{"value": "test-uuid"}], + "revision_id": [{"value": 123456}], + "org_id": [{"value": 1234}], + "honorific": ["test"], + "first_name": [{"value": "TestFirstName"}], + "last_name": [{"value": "TestLastName"}], + "name_suffix": ["TestSuffix"], + "email": [{"value": "test_abc@test.com"}], + "mobile_phone": [], + "alternate_phone": [], + "twitter_handle": [], + "street_address": [], + "city": [], + "region": [], + "country": [], + "postal_code": [{"value": "test_postal_code"}], + "latitude": [], + "longitude": [], + "opt_in": [{"value": True}], + "nm_product_opt_in": [{"value": False}], + "nm_marketing_opt_in": [{"value": False}], + "groups": [ + { + "target_id": 1234, + "target_type": "contact_group", + "target_uuid": "test-uuid", + "url": "/contact-group/1234", + } + ], + "created": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], + "changed": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], + "prefill_hash": [{"value": "test-value"}], + "subscriber": [], + "sync_status": [[]], + "entitygroupfield": [ + { + "target_id": 1234567, + "target_type": "group_target", + "target_uuid": "test-value", + "url": "/group/1234/content/1234567", + } + ], + }, + "links": { + "Facebook": { + "label": "Share on Facebook", + "url": "https://www.facebook.com/sharer.php?s=100&u=https://win.newmode.net/test", + "title": "", + }, + "Twitter": { + "label": "Tweet to your followers", + "url": "https://nwmd.social/s/twitter/test", + "title": "", + }, + "Email": { + "label": "Send an email", + "url": "https://nwmd.social/s/email/test", + "title": "Add your voice to this campaign!", + }, + "Copy Link": { + "label": "Copy Link", + "url": "https://nwmd.social/s/copylink/test", + "title": "", + }, + }, + "message": "Already submitted", + "submission": { + "sid": [{"value": 123456}], + "uuid": [{"value": "test-value"}], + "revision_id": [{"value": 123456}], + "action_id": [ + { + "target_id": 1234, + "target_type": "node", + "target_uuid": "test-value", + "url": "/node/1234", + } + ], + "contact_id": [ + { + "target_id": 1234567, + "target_type": "contact", + "target_uuid": "test-value", + "url": "/contact/1234567", + } + ], + "status": [ + { + "target_id": 12, + "target_type": "test-value", + "target_uuid": "test-value", + "url": "/taxonomy/term/12", + } + ], + "testmode": [{"value": False}], + "edited": [{"value": True}], + "device": [], + "browser": [], + "browser_version": [], + "os": [], + "os_version": [], + "parent_url": [], + "source_code": [], + "search_value": [], + "created": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], + "changed": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], + "entitygroupfield": [ + { + "target_id": 12345678, + "target_type": "group_content", + "target_uuid": "test-value", + "url": "test-url", + } + ], + }, + "ref_id": "test-value", + } + tbl = Table([json_response]) + m.post(V2_API_AUTH_URL, json={"access_token": "fakeAccessToken"}) + m.get( + f"{self.base_url}/campaign/{self.campaign_id}/form", + json=json_response, + ) + assert_matching_tables(self.nm.get_campaign(campaign_id=self.campaign_id), tbl) + + @requests_mock.Mocker() + def test_get_campaign_ids(self, m): + lst = ["testCampaingID"] + json_response = { + "jsonapi": { + "version": "1.0", + "meta": {"links": {"self": {"href": "http://jsonapi.org/format/1.0/"}}}, + }, + "data": { + "type": "node--action", + "id": "testCampaingID", + "links": { + "self": { + "href": "https://base.newmode.net/jsonapi/node/action/testCampaingID?resourceVersion=id%test" + } + }, + "attributes": {}, + "relationships": {}, + }, + } + m.post(V2_API_AUTH_URL, json={"access_token": "fakeAccessToken"}) + m.get(f"{V2_API_CAMPAIGNS_URL}jsonapi/node/action", json=json_response) + assert_matching_tables(self.nm.get_campaign_ids(), lst) + + @requests_mock.Mocker() + def test_get_recipient(self, m): + self.city = "Vancouver" + json_response = { + "subject": "test subject", + "message": "

Dear [send:full_name],
I know that you care about this example as much as I do.

\n

[contact:full_name]
[contact:email], [contact:full_address]

, subscriber_text_lb", + "id": "b3fc-xxxxxxxxxxxxxxxxxxxxxx-99a8", + "first_name": "Darcy", + "last_name": "Doogoode", + "full_name": "Darcy Doogoode", + "position": "MP", + "party": "Liberal", + "jurisdiction": "Vancouver East", + "rendered": "Darcy Doogoode (MP), Vancouver East, Liberal", + } + + tbl = Table([json_response]) + m.post(V2_API_AUTH_URL, json={"access_token": "fakeAccessToken"}) + m.get(f"{self.base_url}/campaign/{self.campaign_id}/target", json=json_response) + assert_matching_tables( + self.nm.get_recipient(campaign_id=self.campaign_id, city=self.city), tbl + ) + + @requests_mock.Mocker() + def test_run_submit(self, m): + json_response = { + "contact": { + "id": [{"value": 1883}], + "uuid": [{"value": "2efe-xxxxxxxxxxxxxxxxxxxxxx-2044"}], + "revision_id": [{"value": 1954}], + "org_id": [{"value": 1}], + "honorific": [], + "first_name": [{"value": "Sammy"}], + "last_name": [{"value": "Supporter"}], + "name_suffix": [], + "email": [{"value": "test_abc@test.com"}], + "mobile_phone": [], + "alternate_phone": [], + "twitter_handle": [], + "street_address": [{"value": "312 Main Street"}], + "city": [{"value": "Vancouver"}], + "region": [{"value": "BC"}], + "country": [{"value": "CA"}], + "postal_code": [{"value": "V6A 2T2"}], + "latitude": [{"value": 49.282039}], + "longitude": [{"value": -123.099221}], + "opt_in": [{"value": True}], + "nm_product_opt_in": [{"value": True}], + "nm_marketing_opt_in": [{"value": True}], + "groups": [ + { + "target_id": 58, + "target_type": "contact_group", + "target_uuid": "f426-xxxxxxxxxxxxxxxxxxxxxx-6712", + "url": "/contact-group/58", + } + ], + "created": [{"value": "1730818224", "format": "Y-m-d\\TH:i:sP"}], + "changed": [{"value": "1730818779", "format": "Y-m-d\\TH:i:sP"}], + "prefill_hash": [ + { + "value": "706a1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxe501" + } + ], + "subscriber": [], + "sync_status": [[]], + "entitygroupfield": [ + { + "target_id": 5648, + "target_type": "group_content", + "target_uuid": "68be-xxxxxxxxxxxxxxxxxxxxxx-095c", + "url": "/group/1/content/5648", + } + ], + }, + "submission": { + "sid": [{"value": 692}], + "uuid": [{"value": "364a-xxxxxxxxxxxxxxxxxxxxxx-d545"}], + "revision_id": [{"value": 692}], + "action_id": [ + { + "target_id": 197, + "target_type": "node", + "target_uuid": "54f7-xxxxxxxxxxxxxxxxxxxxxx-b11f", + "url": "/node/197", + } + ], + "contact_id": [ + { + "target_id": 1883, + "target_type": "contact", + "target_uuid": "2efe-xxxxxxxxxxxxxxxxxxxxxx-2044", + "url": "/contact/1883", + } + ], + "status": [ + { + "target_id": 78, + "target_type": "taxonomy_term", + "target_uuid": "1sb6-xxxxxxxxxxxxxxxxxxxxxx-ba19", + "url": "/taxonomy/term/78", + } + ], + "testmode": [{"value": False}], + "edited": [{"value": False}], + "device": [{"value": "PC"}], + "browser": [{"value": "Firefox"}], + "browser_version": [{"value": "132.0"}], + "os": [{"value": "GNU/Linux"}], + "os_version": [], + "parent_url": [{"value": "https://www.mysite.com/mycampaign"}], + "source_code": [{"value": "facebook"}], + "search_value": [], + "created": [{"value": "1730818779", "format": "Y-m-d\\TH:i:sP"}], + "changed": [{"value": "1730818779", "format": "Y-m-d\\TH:i:sP"}], + "entitygroupfield": [ + { + "target_id": 5652, + "target_type": "group_content", + "target_uuid": "2119-xxxxxxxxxxxxxxxxxxxxxx-ce92", + "url": "/group/1/content/5xx2", + } + ], + }, + "links": { + "Facebook": { + "label": "Share on Facebook", + "url": "https://www.facebook.com/sharer.php?s=100&u=https://www.mysite.com/mycampaign", + "title": "", + }, + "Twitter": { + "label": "Tweet to your followers", + "url": "http://base.test:8020/s/twitter/9VI1xxxxxxxxxg=/b", + "title": "", + }, + "Email": { + "label": "Send an email", + "url": "http://base.test:8020/s/email/9VI1xxxxxxxxxg=/b", + "title": "Add your voice to this campaign!", + }, + "Copy Link": { + "label": "Copy Link", + "url": "http://base.test:8020/s/copylink/9VI1MbcwMCg=/b", + "title": "", + }, + }, + "queue_id": "3xx6", + "ref_id": "706axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxe501", + } + json_response = { + "action_id": self.campaign_id, + "first_name": "TestFirstName", + "last_name": "TestLastName", + "email": "test_abc@test.com", + "opt_in": 1, + "address": {"postal_code": "V6A 2T2"}, + "subject": "This is my subject", + "message": "This is my letter", + } + + m.post(V2_API_AUTH_URL, json={"access_token": "fakeAccessToken"}) + m.post( + f"{self.base_url}/campaign/{self.campaign_id}/submit", + json=json_response, + ) + assert_matching_tables( + self.nm.run_submit(campaign_id=self.campaign_id, json=json_response), + json_response, + ) diff --git a/test/test_newmode/test_newmode_v2.py b/test/test_newmode/test_newmode_v2.py deleted file mode 100644 index 6590289c11..0000000000 --- a/test/test_newmode/test_newmode_v2.py +++ /dev/null @@ -1,347 +0,0 @@ -import unittest -from test.utils import assert_matching_tables - -import requests_mock -from parsons import Table, NewmodeV2 - -CLIENT_ID = "fakeClientID" -CLIENT_SECRET = "fakeClientSecret" - - -API_URL = "https://base.newmode.net/api/" -API_AUTH_URL = "https://base.newmode.net/oauth/token/" -API_CAMPAIGNS_URL = "https://base.newmode.net/" - - -class TestZoom(unittest.TestCase): - @requests_mock.Mocker() - def setUp(self, m): - m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - self.nm = NewmodeV2(CLIENT_ID, CLIENT_SECRET) - self.campaign_id = "fakeCampaignID" - self.api_version = "v2.1" - self.base_url = f"{API_URL}{self.api_version}" - - @requests_mock.Mocker() - def test_get_campaign(self, m): - json_response = { - "contact": { - "id": [{"value": 12345678}], - "uuid": [{"value": "test-uuid"}], - "revision_id": [{"value": 123456}], - "org_id": [{"value": 1234}], - "honorific": ["test"], - "first_name": [{"value": "TestFirstName"}], - "last_name": [{"value": "TestLastName"}], - "name_suffix": ["TestSuffix"], - "email": [{"value": "test_abc@test.com"}], - "mobile_phone": [], - "alternate_phone": [], - "twitter_handle": [], - "street_address": [], - "city": [], - "region": [], - "country": [], - "postal_code": [{"value": "test_postal_code"}], - "latitude": [], - "longitude": [], - "opt_in": [{"value": True}], - "nm_product_opt_in": [{"value": False}], - "nm_marketing_opt_in": [{"value": False}], - "groups": [ - { - "target_id": 1234, - "target_type": "contact_group", - "target_uuid": "test-uuid", - "url": "/contact-group/1234", - } - ], - "created": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], - "changed": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], - "prefill_hash": [{"value": "test-value"}], - "subscriber": [], - "sync_status": [[]], - "entitygroupfield": [ - { - "target_id": 1234567, - "target_type": "group_target", - "target_uuid": "test-value", - "url": "/group/1234/content/1234567", - } - ], - }, - "links": { - "Facebook": { - "label": "Share on Facebook", - "url": "https://www.facebook.com/sharer.php?s=100&u=https://win.newmode.net/test", - "title": "", - }, - "Twitter": { - "label": "Tweet to your followers", - "url": "https://nwmd.social/s/twitter/test", - "title": "", - }, - "Email": { - "label": "Send an email", - "url": "https://nwmd.social/s/email/test", - "title": "Add your voice to this campaign!", - }, - "Copy Link": { - "label": "Copy Link", - "url": "https://nwmd.social/s/copylink/test", - "title": "", - }, - }, - "message": "Already submitted", - "submission": { - "sid": [{"value": 123456}], - "uuid": [{"value": "test-value"}], - "revision_id": [{"value": 123456}], - "action_id": [ - { - "target_id": 1234, - "target_type": "node", - "target_uuid": "test-value", - "url": "/node/1234", - } - ], - "contact_id": [ - { - "target_id": 1234567, - "target_type": "contact", - "target_uuid": "test-value", - "url": "/contact/1234567", - } - ], - "status": [ - { - "target_id": 12, - "target_type": "test-value", - "target_uuid": "test-value", - "url": "/taxonomy/term/12", - } - ], - "testmode": [{"value": False}], - "edited": [{"value": True}], - "device": [], - "browser": [], - "browser_version": [], - "os": [], - "os_version": [], - "parent_url": [], - "source_code": [], - "search_value": [], - "created": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], - "changed": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], - "entitygroupfield": [ - { - "target_id": 12345678, - "target_type": "group_content", - "target_uuid": "test-value", - "url": "test-url", - } - ], - }, - "ref_id": "test-value", - } - tbl = Table([json_response]) - m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - m.get( - f"{self.base_url}/campaign/{self.campaign_id}/form", - json=json_response, - ) - assert_matching_tables(self.nm.get_campaign(campaign_id=self.campaign_id), tbl) - - @requests_mock.Mocker() - def test_get_campaigns(self, m): - lst = ["testCampaingID"] - json_response = { - "jsonapi": { - "version": "1.0", - "meta": {"links": {"self": {"href": "http://jsonapi.org/format/1.0/"}}}, - }, - "data": { - "type": "node--action", - "id": "testCampaingID", - "links": { - "self": { - "href": "https://base.newmode.net/jsonapi/node/action/testCampaingID?resourceVersion=id%test" - } - }, - "attributes": {}, - "relationships": {}, - }, - } - m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - m.get(f"{API_CAMPAIGNS_URL}jsonapi/node/action", json=json_response) - assert_matching_tables(self.nm.get_campaigns(), lst) - - @requests_mock.Mocker() - def test_get_recipient(self, m): - self.city = "Vancouver" - json_response = { - "subject": "test subject", - "message": "

Dear [send:full_name],
I know that you care about this example as much as I do.

\n

[contact:full_name]
[contact:email], [contact:full_address]

, subscriber_text_lb", - "id": "b3fc-xxxxxxxxxxxxxxxxxxxxxx-99a8", - "first_name": "Darcy", - "last_name": "Doogoode", - "full_name": "Darcy Doogoode", - "position": "MP", - "party": "Liberal", - "jurisdiction": "Vancouver East", - "rendered": "Darcy Doogoode (MP), Vancouver East, Liberal", - } - - tbl = Table([json_response]) - m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - m.get(f"{self.base_url}/campaign/{self.campaign_id}/target", json=json_response) - assert_matching_tables( - self.nm.get_recipient(campaign_id=self.campaign_id, city=self.city), tbl - ) - - @requests_mock.Mocker() - def test_run_submit(self, m): - json_response = { - "contact": { - "id": [{"value": 1883}], - "uuid": [{"value": "2efe-xxxxxxxxxxxxxxxxxxxxxx-2044"}], - "revision_id": [{"value": 1954}], - "org_id": [{"value": 1}], - "honorific": [], - "first_name": [{"value": "Sammy"}], - "last_name": [{"value": "Supporter"}], - "name_suffix": [], - "email": [{"value": "test_abc@test.com"}], - "mobile_phone": [], - "alternate_phone": [], - "twitter_handle": [], - "street_address": [{"value": "312 Main Street"}], - "city": [{"value": "Vancouver"}], - "region": [{"value": "BC"}], - "country": [{"value": "CA"}], - "postal_code": [{"value": "V6A 2T2"}], - "latitude": [{"value": 49.282039}], - "longitude": [{"value": -123.099221}], - "opt_in": [{"value": True}], - "nm_product_opt_in": [{"value": True}], - "nm_marketing_opt_in": [{"value": True}], - "groups": [ - { - "target_id": 58, - "target_type": "contact_group", - "target_uuid": "f426-xxxxxxxxxxxxxxxxxxxxxx-6712", - "url": "/contact-group/58", - } - ], - "created": [{"value": "1730818224", "format": "Y-m-d\\TH:i:sP"}], - "changed": [{"value": "1730818779", "format": "Y-m-d\\TH:i:sP"}], - "prefill_hash": [ - { - "value": "706a1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxe501" - } - ], - "subscriber": [], - "sync_status": [[]], - "entitygroupfield": [ - { - "target_id": 5648, - "target_type": "group_content", - "target_uuid": "68be-xxxxxxxxxxxxxxxxxxxxxx-095c", - "url": "/group/1/content/5648", - } - ], - }, - "submission": { - "sid": [{"value": 692}], - "uuid": [{"value": "364a-xxxxxxxxxxxxxxxxxxxxxx-d545"}], - "revision_id": [{"value": 692}], - "action_id": [ - { - "target_id": 197, - "target_type": "node", - "target_uuid": "54f7-xxxxxxxxxxxxxxxxxxxxxx-b11f", - "url": "/node/197", - } - ], - "contact_id": [ - { - "target_id": 1883, - "target_type": "contact", - "target_uuid": "2efe-xxxxxxxxxxxxxxxxxxxxxx-2044", - "url": "/contact/1883", - } - ], - "status": [ - { - "target_id": 78, - "target_type": "taxonomy_term", - "target_uuid": "1sb6-xxxxxxxxxxxxxxxxxxxxxx-ba19", - "url": "/taxonomy/term/78", - } - ], - "testmode": [{"value": False}], - "edited": [{"value": False}], - "device": [{"value": "PC"}], - "browser": [{"value": "Firefox"}], - "browser_version": [{"value": "132.0"}], - "os": [{"value": "GNU/Linux"}], - "os_version": [], - "parent_url": [{"value": "https://www.mysite.com/mycampaign"}], - "source_code": [{"value": "facebook"}], - "search_value": [], - "created": [{"value": "1730818779", "format": "Y-m-d\\TH:i:sP"}], - "changed": [{"value": "1730818779", "format": "Y-m-d\\TH:i:sP"}], - "entitygroupfield": [ - { - "target_id": 5652, - "target_type": "group_content", - "target_uuid": "2119-xxxxxxxxxxxxxxxxxxxxxx-ce92", - "url": "/group/1/content/5xx2", - } - ], - }, - "links": { - "Facebook": { - "label": "Share on Facebook", - "url": "https://www.facebook.com/sharer.php?s=100&u=https://www.mysite.com/mycampaign", - "title": "", - }, - "Twitter": { - "label": "Tweet to your followers", - "url": "http://base.test:8020/s/twitter/9VI1xxxxxxxxxg=/b", - "title": "", - }, - "Email": { - "label": "Send an email", - "url": "http://base.test:8020/s/email/9VI1xxxxxxxxxg=/b", - "title": "Add your voice to this campaign!", - }, - "Copy Link": { - "label": "Copy Link", - "url": "http://base.test:8020/s/copylink/9VI1MbcwMCg=/b", - "title": "", - }, - }, - "queue_id": "3xx6", - "ref_id": "706axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxe501", - } - json_response = { - "action_id": self.campaign_id, - "first_name": "TestFirstName", - "last_name": "TestLastName", - "email": "test_abc@test.com", - "opt_in": 1, - "address": {"postal_code": "V6A 2T2"}, - "subject": "This is my subject", - "message": "This is my letter", - } - - m.post(API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - m.post( - f"{self.base_url}/campaign/{self.campaign_id}/submit", - json=json_response, - ) - assert_matching_tables( - self.nm.run_submit(campaign_id=self.campaign_id, json=json_response), - json_response, - ) From 606f7e0fda958cb9e102bfcf561c972a0eb260fe Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Sun, 5 Jan 2025 19:09:47 -0800 Subject: [PATCH 08/30] clean up --- parsons/newmode/newmode.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index ee2bd260d6..34c8f4055c 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -27,7 +27,9 @@ def __init__(self, api_user=None, api_password=None, api_version=None): Returns: Newmode class """ - logger.warning("Newmode V1 API will be sunset in Feburary 2025.") + logger.warning( + "Newmode V1 API will be sunset in Feburary 2025. To use V2, set api_version=v2.1" + ) self.api_user = check_env.check("NEWMODE_API_USER", api_user) self.api_password = check_env.check("NEWMODE_API_PASSWORD", api_password) self.api_version = check_env.check("NEWMODE_API_VERSION", api_version) From 6a48b2afb6e89599adc1ca9d092dfd1623181b89 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Sun, 5 Jan 2025 19:13:58 -0800 Subject: [PATCH 09/30] add docstrings --- parsons/newmode/newmode.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index 34c8f4055c..b5a95c9fcb 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -367,6 +367,9 @@ def convert_to_table(self, data): return table def base_request(self, method, url, data=None, json=None, data_key=None, params={}): + """ + Internal method to make a call to Newmode API and validate the response + """ response = None response = self.client.request( url=url, req_type=method, json=json, data=data, params=params @@ -393,6 +396,7 @@ def converted_request( convert_to_table=True, data_key=None, ): + """Internal method to make a call to the Newmode API and convert the result to a Parsons table.""" self.client = OAuth2APIConnector( uri=self.base_url, auto_refresh_url=V2_API_AUTH_URL, From 8d0bb95f61e33d90408c140e0a50baa6dd70da64 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Sun, 5 Jan 2025 19:24:46 -0800 Subject: [PATCH 10/30] clean up docstrings + unittests --- parsons/newmode/newmode.py | 32 ++++++++++++++++++++++++++----- test/test_newmode/test_newmode.py | 7 +++---- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index b5a95c9fcb..04f947a699 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -339,14 +339,14 @@ def __init__( """ Instantiate Class `Args`: - api_user: str - The username to use for the API requests. Not required if ``NEWMODE_V2_API_URL`` + client_id: str + The client id to use for the API requests. Not required if ``NEWMODE_API_CLIENT_ID`` env variable set. - api_password: str - The password to use for the API requests. Not required if ``NEWMODE_API_PASSWORD`` + client_secret: str + The client secret to use for the API requests. Not required if ``NEWMODE_API_CLIENT_SECRET`` env variable set. api_version: str - The api version to use. Defaults to v1.0 + The api version to use. Defaults to v2.1 Returns: NewMode Class """ @@ -566,6 +566,28 @@ def __new__( api_password=None, api_version="v1.0", ): + """ + Create and return Newmode instance based on chosen version (V1 or V2) + + `Args`: + api_user: str + The Newmode api user. Not required if ``NEWMODE_API_USER`` env variable is + passed. + api_password: str + The Newmode api password. Not required if ``NEWMODE_API_PASSWORD`` env variable is + passed. + client_id: str + The client id to use for the API requests. Not required if ``NEWMODE_API_CLIENT_ID`` + env variable set. + client_secret: str + The client secret to use for the API requests. Not required if ``NEWMODE_API_CLIENT_SECRET`` + env variable set. + api_version: str + The api version to use. Defaults to v1.0 + + Returns: + NewMode Class + """ if "v2" in api_version: return NewmodeV2( client_id=client_id, client_secret=client_secret, api_version=api_version diff --git a/test/test_newmode/test_newmode.py b/test/test_newmode/test_newmode.py index 71f5c21136..e8a9b57c08 100644 --- a/test/test_newmode/test_newmode.py +++ b/test/test_newmode/test_newmode.py @@ -2,7 +2,7 @@ import unittest import unittest.mock as mock import requests_mock -from parsons import NewmodeV1, NewmodeV2 +from parsons import Newmode from test.utils import assert_matching_tables CLIENT_ID = "fakeClientID" @@ -19,7 +19,7 @@ def setUp(self): os.environ["NEWMODE_API_USER"] = "MYFAKEUSERNAME" os.environ["NEWMODE_API_PASSWORD"] = "MYFAKEPASSWORD" - self.nm = NewmodeV1() + self.nm = Newmode() self.nm.client = mock.MagicMock() self.nm.client.getTools.return_value = [ @@ -174,9 +174,8 @@ class TestNewmodeV2(unittest.TestCase): @requests_mock.Mocker() def setUp(self, m): m.post(V2_API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - self.nm = NewmodeV2(CLIENT_ID, CLIENT_SECRET) + self.nm = Newmode(client_id=CLIENT_ID, client_secret=CLIENT_SECRET, api_version="v2.1") self.campaign_id = "fakeCampaignID" - self.api_version = "v2.1" self.base_url = f"{V2_API_URL}{self.api_version}" @requests_mock.Mocker() From afe9716421bd1283f2dc337efaf623377660b370 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Sun, 5 Jan 2025 19:29:28 -0800 Subject: [PATCH 11/30] clean up --- test/test_newmode/test_newmode.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/test_newmode/test_newmode.py b/test/test_newmode/test_newmode.py index e8a9b57c08..d2c569d4af 100644 --- a/test/test_newmode/test_newmode.py +++ b/test/test_newmode/test_newmode.py @@ -174,9 +174,10 @@ class TestNewmodeV2(unittest.TestCase): @requests_mock.Mocker() def setUp(self, m): m.post(V2_API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - self.nm = Newmode(client_id=CLIENT_ID, client_secret=CLIENT_SECRET, api_version="v2.1") + api_version="v2.1" + self.nm = Newmode(client_id=CLIENT_ID, client_secret=CLIENT_SECRET, api_version=api_version) self.campaign_id = "fakeCampaignID" - self.base_url = f"{V2_API_URL}{self.api_version}" + self.base_url = f"{V2_API_URL}{api_version}" @requests_mock.Mocker() def test_get_campaign(self, m): From 9a351e0e34372289b96cb4cc92af818c7d687298 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Sun, 5 Jan 2025 19:30:32 -0800 Subject: [PATCH 12/30] ruff --- parsons/newmode/newmode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index 04f947a699..9cb2197510 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -584,7 +584,7 @@ def __new__( env variable set. api_version: str The api version to use. Defaults to v1.0 - + Returns: NewMode Class """ From fa414d944a8d3cff78dd98855816033470b01093 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Sun, 5 Jan 2025 19:32:48 -0800 Subject: [PATCH 13/30] clean up test --- test/test_newmode/test_newmode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_newmode/test_newmode.py b/test/test_newmode/test_newmode.py index d2c569d4af..8124255885 100644 --- a/test/test_newmode/test_newmode.py +++ b/test/test_newmode/test_newmode.py @@ -2,7 +2,7 @@ import unittest import unittest.mock as mock import requests_mock -from parsons import Newmode +from parsons import Newmode, Table from test.utils import assert_matching_tables CLIENT_ID = "fakeClientID" From 362bbb9ef35e26a64e3403ac664867de2683a3c2 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Sun, 5 Jan 2025 19:35:16 -0800 Subject: [PATCH 14/30] ruff --- test/test_newmode/test_newmode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_newmode/test_newmode.py b/test/test_newmode/test_newmode.py index 8124255885..1d8ab747a0 100644 --- a/test/test_newmode/test_newmode.py +++ b/test/test_newmode/test_newmode.py @@ -174,7 +174,7 @@ class TestNewmodeV2(unittest.TestCase): @requests_mock.Mocker() def setUp(self, m): m.post(V2_API_AUTH_URL, json={"access_token": "fakeAccessToken"}) - api_version="v2.1" + api_version = "v2.1" self.nm = Newmode(client_id=CLIENT_ID, client_secret=CLIENT_SECRET, api_version=api_version) self.campaign_id = "fakeCampaignID" self.base_url = f"{V2_API_URL}{api_version}" From a9a99ba8aa590e50057c2f16b8e14cd153fa541d Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Mon, 6 Jan 2025 15:47:36 -0800 Subject: [PATCH 15/30] clean up --- parsons/newmode/newmode.py | 134 ++++++++------ parsons/utilities/api_connector.py | 11 ++ test/test_newmode/test_data.py | 274 ++++++++++++++++++++++++++++ test/test_newmode/test_newmode.py | 282 +---------------------------- 4 files changed, 368 insertions(+), 333 deletions(-) create mode 100644 test/test_newmode/test_data.py diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index 9cb2197510..e9d7cbc27b 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -9,6 +9,12 @@ V2_API_URL = "https://base.newmode.net/api/" V2_API_AUTH_URL = "https://base.newmode.net/oauth/token/" V2_API_CAMPAIGNS_URL = "https://base.newmode.net/" +V2_API_CAMPAIGNS_VERSION = "jsonapi" +V2_API_CAMPAIGNS_HEADERS = { + "content-type": "application/vnd.api+json", + "accept": "application/vnd.api+json", + "authorization": "Bearer 1234567890", +} class NewmodeV1: @@ -32,8 +38,7 @@ def __init__(self, api_user=None, api_password=None, api_version=None): ) self.api_user = check_env.check("NEWMODE_API_USER", api_user) self.api_password = check_env.check("NEWMODE_API_PASSWORD", api_password) - self.api_version = check_env.check("NEWMODE_API_VERSION", api_version) - + self.api_version = api_version self.client = Client(api_user, api_password, api_version) def convert_to_table(self, data): @@ -330,6 +335,7 @@ def get_outreach(self, outreach_id, params={}): class NewmodeV2: + # TODO: Add param definition and requirements once official Newmode docs are published def __init__( self, client_id=None, @@ -350,27 +356,48 @@ def __init__( Returns: NewMode Class """ - self.api_version = check_env.check("NEWMODE_API_VERSION", api_version) + self.api_version = api_version self.base_url = V2_API_URL self.client_id = check_env.check("NEWMODE_API_CLIENT_ID", client_id) - self.__client_secret = check_env.check("NEWMODE_API_CLIENT_SECRET", client_secret) + self.client_secret = check_env.check("NEWMODE_API_CLIENT_SECRET", client_secret) self.headers = {"content-type": "application/json"} - def convert_to_table(self, data): - """Internal method to create a Parsons table from a data element.""" - table = None - if type(data) is list: - table = Table(data) + def base_request( + self, + method, + url, + endpoint, + data=None, + json=None, + data_key=None, + params={}, + is_get_campaign_ids_method=False, + supports_version=True, + ): + """ + Internal method to instantiate OAuth2APIConnector class, + make a call to Newmode API, and validate the response. + """ + if is_get_campaign_ids_method: + base_uri = V2_API_CAMPAIGNS_URL + api_version = V2_API_CAMPAIGNS_VERSION + headers = V2_API_CAMPAIGNS_HEADERS else: - table = Table([data]) + base_uri = self.base_url + api_version = self.api_version + headers = self.headers - return table + self.client = OAuth2APIConnector( + uri=base_uri, + auto_refresh_url=V2_API_AUTH_URL, + client_id=self.client_id, + client_secret=self.client_secret, + headers=headers, + token_url=V2_API_AUTH_URL, + grant_type="client_credentials", + ) - def base_request(self, method, url, data=None, json=None, data_key=None, params={}): - """ - Internal method to make a call to Newmode API and validate the response - """ - response = None + url = f"{api_version}/{endpoint}" if supports_version else endpoint response = self.client.request( url=url, req_type=method, json=json, data=data, params=params ) @@ -378,12 +405,9 @@ def base_request(self, method, url, data=None, json=None, data_key=None, params= success_codes = [200, 201, 202, 204] self.client.validate_response(response) if response.status_code in success_codes: - if self.client.json_check(response): - response_json = response.json() - return response_json[data_key] if data_key else response_json - else: - return response.status_code - return response + response_json = response.json() if self.client.json_check(response) else None + return response_json[data_key] if data_key and response_json else response_json + raise Exception(f"API request encountered an error. Response: {response}") def converted_request( self, @@ -395,31 +419,21 @@ def converted_request( params={}, convert_to_table=True, data_key=None, + is_get_campaign_ids_method=False, ): """Internal method to make a call to the Newmode API and convert the result to a Parsons table.""" - self.client = OAuth2APIConnector( - uri=self.base_url, - auto_refresh_url=V2_API_AUTH_URL, - client_id=self.client_id, - client_secret=self.__client_secret, - headers=self.headers, - token_url=V2_API_AUTH_URL, - grant_type="client_credentials", - ) - - url = f"{self.api_version}/{endpoint}" if supports_version else endpoint response = self.base_request( method=method, - url=url, json=json, data=data, params=params, data_key=data_key, + is_get_campaign_ids_method=is_get_campaign_ids_method, + supports_version=supports_version, + endpoint=endpoint, ) - if not response: - logging.warning(f"Empty result returned from endpoint: {endpoint}") if convert_to_table: - return self.convert_to_table(response) + return OAuth2APIConnector.convert_to_table(response) else: return response @@ -437,12 +451,12 @@ def get_campaign(self, campaign_id, params={}): Parsons Table containing campaign data. """ endpoint = f"/campaign/{campaign_id}/form" - response = self.converted_request( + data = self.converted_request( endpoint=endpoint, method="GET", params=params, ) - return response + return data def get_campaign_ids(self, params={}): """ @@ -456,18 +470,15 @@ def get_campaign_ids(self, params={}): `Returns:` List containing all campaign ids. """ - self.base_url = V2_API_CAMPAIGNS_URL - self.api_version = "jsonapi" - self.headers = { - "content-type": "application/vnd.api+json", - "accept": "application/vnd.api+json", - "authorization": "Bearer 1234567890", - } endpoint = "node/action" - response = self.converted_request( - endpoint=endpoint, method="GET", params=params, data_key="data" + data = self.converted_request( + endpoint=endpoint, + method="GET", + params=params, + data_key="data", + is_get_campaign_ids_method=True, ) - return response["id"] + return data["id"] def get_recipient( self, @@ -502,9 +513,11 @@ def get_recipient( "postal_code": postal_code, "region": region, } - if all(x is None for x in address_params.values()): - logger.error("Please specify a street address, city, postal code, and/or region.") - raise Exception("Incomplete Request") + all_address_params_are_missing = all(x is None for x in address_params.values()) + if all_address_params_are_missing: + raise ValueError( + "Incomplete Request. Please specify a street address, city, postal code, and/or region." + ) params = {f"address[value][{key}]": value for key, value in address_params.items() if value} response = self.converted_request( @@ -572,25 +585,30 @@ def __new__( `Args`: api_user: str The Newmode api user. Not required if ``NEWMODE_API_USER`` env variable is - passed. + passed. Needed for V1. api_password: str The Newmode api password. Not required if ``NEWMODE_API_PASSWORD`` env variable is - passed. + passed. Needed for V1. client_id: str The client id to use for the API requests. Not required if ``NEWMODE_API_CLIENT_ID`` - env variable set. + env variable set. Needed for V2. client_secret: str The client secret to use for the API requests. Not required if ``NEWMODE_API_CLIENT_SECRET`` - env variable set. + env variable set. Needed for V2. api_version: str - The api version to use. Defaults to v1.0 + The api version to use. Defaults to v1.0. Returns: NewMode Class """ - if "v2" in api_version: + api_version = check_env.check("NEWMODE_API_VERSION", api_version) + if api_version.startswith("v2"): + if not client_id or client_secret: + raise ValueError("Missing client_id and/or client_secret") return NewmodeV2( client_id=client_id, client_secret=client_secret, api_version=api_version ) else: + if not api_user or api_password: + raise ValueError("Missing api_user and/or api_password") return NewmodeV1(api_user=api_user, api_password=api_password, api_version=api_version) diff --git a/parsons/utilities/api_connector.py b/parsons/utilities/api_connector.py index 39564cc20b..54a7479e9e 100644 --- a/parsons/utilities/api_connector.py +++ b/parsons/utilities/api_connector.py @@ -3,6 +3,7 @@ import logging import urllib.parse from simplejson.errors import JSONDecodeError +from parsons import Table logger = logging.getLogger(__name__) @@ -306,3 +307,13 @@ def json_check(self, resp): return True except JSONDecodeError: return False + + def convert_to_table(self, data): + """Internal method to create a Parsons table from a data element.""" + table = None + if type(data) is list: + table = Table(data) + else: + table = Table([data]) + + return table \ No newline at end of file diff --git a/test/test_newmode/test_data.py b/test/test_newmode/test_data.py new file mode 100644 index 0000000000..65262d7ff2 --- /dev/null +++ b/test/test_newmode/test_data.py @@ -0,0 +1,274 @@ +get_campaign_json_response = { + "contact": { + "id": [{"value": 12345678}], + "uuid": [{"value": "test-uuid"}], + "revision_id": [{"value": 123456}], + "org_id": [{"value": 1234}], + "honorific": ["test"], + "first_name": [{"value": "TestFirstName"}], + "last_name": [{"value": "TestLastName"}], + "name_suffix": ["TestSuffix"], + "email": [{"value": "test_abc@test.com"}], + "mobile_phone": [], + "alternate_phone": [], + "twitter_handle": [], + "street_address": [], + "city": [], + "region": [], + "country": [], + "postal_code": [{"value": "test_postal_code"}], + "latitude": [], + "longitude": [], + "opt_in": [{"value": True}], + "nm_product_opt_in": [{"value": False}], + "nm_marketing_opt_in": [{"value": False}], + "groups": [ + { + "target_id": 1234, + "target_type": "contact_group", + "target_uuid": "test-uuid", + "url": "/contact-group/1234", + } + ], + "created": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], + "changed": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], + "prefill_hash": [{"value": "test-value"}], + "subscriber": [], + "sync_status": [[]], + "entitygroupfield": [ + { + "target_id": 1234567, + "target_type": "group_target", + "target_uuid": "test-value", + "url": "/group/1234/content/1234567", + } + ], + }, + "links": { + "Facebook": { + "label": "Share on Facebook", + "url": "https://www.facebook.com/sharer.php?s=100&u=https://win.newmode.net/test", + "title": "", + }, + "Twitter": { + "label": "Tweet to your followers", + "url": "https://nwmd.social/s/twitter/test", + "title": "", + }, + "Email": { + "label": "Send an email", + "url": "https://nwmd.social/s/email/test", + "title": "Add your voice to this campaign!", + }, + "Copy Link": { + "label": "Copy Link", + "url": "https://nwmd.social/s/copylink/test", + "title": "", + }, + }, + "message": "Already submitted", + "submission": { + "sid": [{"value": 123456}], + "uuid": [{"value": "test-value"}], + "revision_id": [{"value": 123456}], + "action_id": [ + { + "target_id": 1234, + "target_type": "node", + "target_uuid": "test-value", + "url": "/node/1234", + } + ], + "contact_id": [ + { + "target_id": 1234567, + "target_type": "contact", + "target_uuid": "test-value", + "url": "/contact/1234567", + } + ], + "status": [ + { + "target_id": 12, + "target_type": "test-value", + "target_uuid": "test-value", + "url": "/taxonomy/term/12", + } + ], + "testmode": [{"value": False}], + "edited": [{"value": True}], + "device": [], + "browser": [], + "browser_version": [], + "os": [], + "os_version": [], + "parent_url": [], + "source_code": [], + "search_value": [], + "created": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], + "changed": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], + "entitygroupfield": [ + { + "target_id": 12345678, + "target_type": "group_content", + "target_uuid": "test-value", + "url": "test-url", + } + ], + }, + "ref_id": "test-value", +} +get_campaign_ids_json_response = { + "jsonapi": { + "version": "1.0", + "meta": {"links": {"self": {"href": "http://jsonapi.org/format/1.0/"}}}, + }, + "data": { + "type": "node--action", + "id": "testCampaingID", + "links": { + "self": { + "href": "https://base.newmode.net/jsonapi/node/action/testCampaingID?resourceVersion=id%test" + } + }, + "attributes": {}, + "relationships": {}, + }, +} +get_recipient_json_response = { + "subject": "test subject", + "message": "

Dear [send:full_name],
I know that you care about this example as much as I do.

\n

[contact:full_name]
[contact:email], [contact:full_address]

, subscriber_text_lb", + "id": "b3fc-xxxxxxxxxxxxxxxxxxxxxx-99a8", + "first_name": "Darcy", + "last_name": "Doogoode", + "full_name": "Darcy Doogoode", + "position": "MP", + "party": "Liberal", + "jurisdiction": "Vancouver East", + "rendered": "Darcy Doogoode (MP), Vancouver East, Liberal", +} + +run_submit_json_response = { + "contact": { + "id": [{"value": 1883}], + "uuid": [{"value": "2efe-xxxxxxxxxxxxxxxxxxxxxx-2044"}], + "revision_id": [{"value": 1954}], + "org_id": [{"value": 1}], + "honorific": [], + "first_name": [{"value": "Sammy"}], + "last_name": [{"value": "Supporter"}], + "name_suffix": [], + "email": [{"value": "test_abc@test.com"}], + "mobile_phone": [], + "alternate_phone": [], + "twitter_handle": [], + "street_address": [{"value": "312 Main Street"}], + "city": [{"value": "Vancouver"}], + "region": [{"value": "BC"}], + "country": [{"value": "CA"}], + "postal_code": [{"value": "V6A 2T2"}], + "latitude": [{"value": 49.282039}], + "longitude": [{"value": -123.099221}], + "opt_in": [{"value": True}], + "nm_product_opt_in": [{"value": True}], + "nm_marketing_opt_in": [{"value": True}], + "groups": [ + { + "target_id": 58, + "target_type": "contact_group", + "target_uuid": "f426-xxxxxxxxxxxxxxxxxxxxxx-6712", + "url": "/contact-group/58", + } + ], + "created": [{"value": "1730818224", "format": "Y-m-d\\TH:i:sP"}], + "changed": [{"value": "1730818779", "format": "Y-m-d\\TH:i:sP"}], + "prefill_hash": [ + { + "value": "706a1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxe501" + } + ], + "subscriber": [], + "sync_status": [[]], + "entitygroupfield": [ + { + "target_id": 5648, + "target_type": "group_content", + "target_uuid": "68be-xxxxxxxxxxxxxxxxxxxxxx-095c", + "url": "/group/1/content/5648", + } + ], + }, + "submission": { + "sid": [{"value": 692}], + "uuid": [{"value": "364a-xxxxxxxxxxxxxxxxxxxxxx-d545"}], + "revision_id": [{"value": 692}], + "action_id": [ + { + "target_id": 197, + "target_type": "node", + "target_uuid": "54f7-xxxxxxxxxxxxxxxxxxxxxx-b11f", + "url": "/node/197", + } + ], + "contact_id": [ + { + "target_id": 1883, + "target_type": "contact", + "target_uuid": "2efe-xxxxxxxxxxxxxxxxxxxxxx-2044", + "url": "/contact/1883", + } + ], + "status": [ + { + "target_id": 78, + "target_type": "taxonomy_term", + "target_uuid": "1sb6-xxxxxxxxxxxxxxxxxxxxxx-ba19", + "url": "/taxonomy/term/78", + } + ], + "testmode": [{"value": False}], + "edited": [{"value": False}], + "device": [{"value": "PC"}], + "browser": [{"value": "Firefox"}], + "browser_version": [{"value": "132.0"}], + "os": [{"value": "GNU/Linux"}], + "os_version": [], + "parent_url": [{"value": "https://www.mysite.com/mycampaign"}], + "source_code": [{"value": "facebook"}], + "search_value": [], + "created": [{"value": "1730818779", "format": "Y-m-d\\TH:i:sP"}], + "changed": [{"value": "1730818779", "format": "Y-m-d\\TH:i:sP"}], + "entitygroupfield": [ + { + "target_id": 5652, + "target_type": "group_content", + "target_uuid": "2119-xxxxxxxxxxxxxxxxxxxxxx-ce92", + "url": "/group/1/content/5xx2", + } + ], + }, + "links": { + "Facebook": { + "label": "Share on Facebook", + "url": "https://www.facebook.com/sharer.php?s=100&u=https://www.mysite.com/mycampaign", + "title": "", + }, + "Twitter": { + "label": "Tweet to your followers", + "url": "http://base.test:8020/s/twitter/9VI1xxxxxxxxxg=/b", + "title": "", + }, + "Email": { + "label": "Send an email", + "url": "http://base.test:8020/s/email/9VI1xxxxxxxxxg=/b", + "title": "Add your voice to this campaign!", + }, + "Copy Link": { + "label": "Copy Link", + "url": "http://base.test:8020/s/copylink/9VI1MbcwMCg=/b", + "title": "", + }, + }, + "queue_id": "3xx6", + "ref_id": "706axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxe501", +} diff --git a/test/test_newmode/test_newmode.py b/test/test_newmode/test_newmode.py index 1d8ab747a0..d8a0e6ec52 100644 --- a/test/test_newmode/test_newmode.py +++ b/test/test_newmode/test_newmode.py @@ -4,6 +4,7 @@ import requests_mock from parsons import Newmode, Table from test.utils import assert_matching_tables +import test.test_newmode.test_data as test_data CLIENT_ID = "fakeClientID" CLIENT_SECRET = "fakeClientSecret" @@ -181,126 +182,7 @@ def setUp(self, m): @requests_mock.Mocker() def test_get_campaign(self, m): - json_response = { - "contact": { - "id": [{"value": 12345678}], - "uuid": [{"value": "test-uuid"}], - "revision_id": [{"value": 123456}], - "org_id": [{"value": 1234}], - "honorific": ["test"], - "first_name": [{"value": "TestFirstName"}], - "last_name": [{"value": "TestLastName"}], - "name_suffix": ["TestSuffix"], - "email": [{"value": "test_abc@test.com"}], - "mobile_phone": [], - "alternate_phone": [], - "twitter_handle": [], - "street_address": [], - "city": [], - "region": [], - "country": [], - "postal_code": [{"value": "test_postal_code"}], - "latitude": [], - "longitude": [], - "opt_in": [{"value": True}], - "nm_product_opt_in": [{"value": False}], - "nm_marketing_opt_in": [{"value": False}], - "groups": [ - { - "target_id": 1234, - "target_type": "contact_group", - "target_uuid": "test-uuid", - "url": "/contact-group/1234", - } - ], - "created": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], - "changed": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], - "prefill_hash": [{"value": "test-value"}], - "subscriber": [], - "sync_status": [[]], - "entitygroupfield": [ - { - "target_id": 1234567, - "target_type": "group_target", - "target_uuid": "test-value", - "url": "/group/1234/content/1234567", - } - ], - }, - "links": { - "Facebook": { - "label": "Share on Facebook", - "url": "https://www.facebook.com/sharer.php?s=100&u=https://win.newmode.net/test", - "title": "", - }, - "Twitter": { - "label": "Tweet to your followers", - "url": "https://nwmd.social/s/twitter/test", - "title": "", - }, - "Email": { - "label": "Send an email", - "url": "https://nwmd.social/s/email/test", - "title": "Add your voice to this campaign!", - }, - "Copy Link": { - "label": "Copy Link", - "url": "https://nwmd.social/s/copylink/test", - "title": "", - }, - }, - "message": "Already submitted", - "submission": { - "sid": [{"value": 123456}], - "uuid": [{"value": "test-value"}], - "revision_id": [{"value": 123456}], - "action_id": [ - { - "target_id": 1234, - "target_type": "node", - "target_uuid": "test-value", - "url": "/node/1234", - } - ], - "contact_id": [ - { - "target_id": 1234567, - "target_type": "contact", - "target_uuid": "test-value", - "url": "/contact/1234567", - } - ], - "status": [ - { - "target_id": 12, - "target_type": "test-value", - "target_uuid": "test-value", - "url": "/taxonomy/term/12", - } - ], - "testmode": [{"value": False}], - "edited": [{"value": True}], - "device": [], - "browser": [], - "browser_version": [], - "os": [], - "os_version": [], - "parent_url": [], - "source_code": [], - "search_value": [], - "created": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], - "changed": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], - "entitygroupfield": [ - { - "target_id": 12345678, - "target_type": "group_content", - "target_uuid": "test-value", - "url": "test-url", - } - ], - }, - "ref_id": "test-value", - } + json_response = test_data.get_campaign_json_response tbl = Table([json_response]) m.post(V2_API_AUTH_URL, json={"access_token": "fakeAccessToken"}) m.get( @@ -312,23 +194,7 @@ def test_get_campaign(self, m): @requests_mock.Mocker() def test_get_campaign_ids(self, m): lst = ["testCampaingID"] - json_response = { - "jsonapi": { - "version": "1.0", - "meta": {"links": {"self": {"href": "http://jsonapi.org/format/1.0/"}}}, - }, - "data": { - "type": "node--action", - "id": "testCampaingID", - "links": { - "self": { - "href": "https://base.newmode.net/jsonapi/node/action/testCampaingID?resourceVersion=id%test" - } - }, - "attributes": {}, - "relationships": {}, - }, - } + json_response = test_data.get_campaign_ids_json_response m.post(V2_API_AUTH_URL, json={"access_token": "fakeAccessToken"}) m.get(f"{V2_API_CAMPAIGNS_URL}jsonapi/node/action", json=json_response) assert_matching_tables(self.nm.get_campaign_ids(), lst) @@ -336,18 +202,7 @@ def test_get_campaign_ids(self, m): @requests_mock.Mocker() def test_get_recipient(self, m): self.city = "Vancouver" - json_response = { - "subject": "test subject", - "message": "

Dear [send:full_name],
I know that you care about this example as much as I do.

\n

[contact:full_name]
[contact:email], [contact:full_address]

, subscriber_text_lb", - "id": "b3fc-xxxxxxxxxxxxxxxxxxxxxx-99a8", - "first_name": "Darcy", - "last_name": "Doogoode", - "full_name": "Darcy Doogoode", - "position": "MP", - "party": "Liberal", - "jurisdiction": "Vancouver East", - "rendered": "Darcy Doogoode (MP), Vancouver East, Liberal", - } + json_response = test_data.get_recipient_json_response tbl = Table([json_response]) m.post(V2_API_AUTH_URL, json={"access_token": "fakeAccessToken"}) @@ -358,131 +213,8 @@ def test_get_recipient(self, m): @requests_mock.Mocker() def test_run_submit(self, m): - json_response = { - "contact": { - "id": [{"value": 1883}], - "uuid": [{"value": "2efe-xxxxxxxxxxxxxxxxxxxxxx-2044"}], - "revision_id": [{"value": 1954}], - "org_id": [{"value": 1}], - "honorific": [], - "first_name": [{"value": "Sammy"}], - "last_name": [{"value": "Supporter"}], - "name_suffix": [], - "email": [{"value": "test_abc@test.com"}], - "mobile_phone": [], - "alternate_phone": [], - "twitter_handle": [], - "street_address": [{"value": "312 Main Street"}], - "city": [{"value": "Vancouver"}], - "region": [{"value": "BC"}], - "country": [{"value": "CA"}], - "postal_code": [{"value": "V6A 2T2"}], - "latitude": [{"value": 49.282039}], - "longitude": [{"value": -123.099221}], - "opt_in": [{"value": True}], - "nm_product_opt_in": [{"value": True}], - "nm_marketing_opt_in": [{"value": True}], - "groups": [ - { - "target_id": 58, - "target_type": "contact_group", - "target_uuid": "f426-xxxxxxxxxxxxxxxxxxxxxx-6712", - "url": "/contact-group/58", - } - ], - "created": [{"value": "1730818224", "format": "Y-m-d\\TH:i:sP"}], - "changed": [{"value": "1730818779", "format": "Y-m-d\\TH:i:sP"}], - "prefill_hash": [ - { - "value": "706a1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxe501" - } - ], - "subscriber": [], - "sync_status": [[]], - "entitygroupfield": [ - { - "target_id": 5648, - "target_type": "group_content", - "target_uuid": "68be-xxxxxxxxxxxxxxxxxxxxxx-095c", - "url": "/group/1/content/5648", - } - ], - }, - "submission": { - "sid": [{"value": 692}], - "uuid": [{"value": "364a-xxxxxxxxxxxxxxxxxxxxxx-d545"}], - "revision_id": [{"value": 692}], - "action_id": [ - { - "target_id": 197, - "target_type": "node", - "target_uuid": "54f7-xxxxxxxxxxxxxxxxxxxxxx-b11f", - "url": "/node/197", - } - ], - "contact_id": [ - { - "target_id": 1883, - "target_type": "contact", - "target_uuid": "2efe-xxxxxxxxxxxxxxxxxxxxxx-2044", - "url": "/contact/1883", - } - ], - "status": [ - { - "target_id": 78, - "target_type": "taxonomy_term", - "target_uuid": "1sb6-xxxxxxxxxxxxxxxxxxxxxx-ba19", - "url": "/taxonomy/term/78", - } - ], - "testmode": [{"value": False}], - "edited": [{"value": False}], - "device": [{"value": "PC"}], - "browser": [{"value": "Firefox"}], - "browser_version": [{"value": "132.0"}], - "os": [{"value": "GNU/Linux"}], - "os_version": [], - "parent_url": [{"value": "https://www.mysite.com/mycampaign"}], - "source_code": [{"value": "facebook"}], - "search_value": [], - "created": [{"value": "1730818779", "format": "Y-m-d\\TH:i:sP"}], - "changed": [{"value": "1730818779", "format": "Y-m-d\\TH:i:sP"}], - "entitygroupfield": [ - { - "target_id": 5652, - "target_type": "group_content", - "target_uuid": "2119-xxxxxxxxxxxxxxxxxxxxxx-ce92", - "url": "/group/1/content/5xx2", - } - ], - }, - "links": { - "Facebook": { - "label": "Share on Facebook", - "url": "https://www.facebook.com/sharer.php?s=100&u=https://www.mysite.com/mycampaign", - "title": "", - }, - "Twitter": { - "label": "Tweet to your followers", - "url": "http://base.test:8020/s/twitter/9VI1xxxxxxxxxg=/b", - "title": "", - }, - "Email": { - "label": "Send an email", - "url": "http://base.test:8020/s/email/9VI1xxxxxxxxxg=/b", - "title": "Add your voice to this campaign!", - }, - "Copy Link": { - "label": "Copy Link", - "url": "http://base.test:8020/s/copylink/9VI1MbcwMCg=/b", - "title": "", - }, - }, - "queue_id": "3xx6", - "ref_id": "706axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxe501", - } - json_response = { + json_response = test_data.run_submit_json_response + json_input = { "action_id": self.campaign_id, "first_name": "TestFirstName", "last_name": "TestLastName", @@ -499,6 +231,6 @@ def test_run_submit(self, m): json=json_response, ) assert_matching_tables( - self.nm.run_submit(campaign_id=self.campaign_id, json=json_response), + self.nm.run_submit(campaign_id=self.campaign_id, json=json_input), json_response, ) From 1e7b0307ef9283dc58e97bfca0d7a577dbcd2741 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Mon, 6 Jan 2025 15:50:43 -0800 Subject: [PATCH 16/30] typo --- test/test_newmode/test_newmode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_newmode/test_newmode.py b/test/test_newmode/test_newmode.py index d8a0e6ec52..b3741071f6 100644 --- a/test/test_newmode/test_newmode.py +++ b/test/test_newmode/test_newmode.py @@ -4,7 +4,7 @@ import requests_mock from parsons import Newmode, Table from test.utils import assert_matching_tables -import test.test_newmode.test_data as test_data +from test.test_newmode import test_data CLIENT_ID = "fakeClientID" CLIENT_SECRET = "fakeClientSecret" From 34340f11db62a24bb6bae702a6dfefa2d23f0ee0 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Mon, 6 Jan 2025 15:53:18 -0800 Subject: [PATCH 17/30] fix name --- test/test_newmode/test_newmode.py | 10 +++++----- .../{test_data.py => test_newmode_data.py} | 0 2 files changed, 5 insertions(+), 5 deletions(-) rename test/test_newmode/{test_data.py => test_newmode_data.py} (100%) diff --git a/test/test_newmode/test_newmode.py b/test/test_newmode/test_newmode.py index b3741071f6..b567a11026 100644 --- a/test/test_newmode/test_newmode.py +++ b/test/test_newmode/test_newmode.py @@ -4,7 +4,7 @@ import requests_mock from parsons import Newmode, Table from test.utils import assert_matching_tables -from test.test_newmode import test_data +from test.test_newmode import test_newmode_data CLIENT_ID = "fakeClientID" CLIENT_SECRET = "fakeClientSecret" @@ -182,7 +182,7 @@ def setUp(self, m): @requests_mock.Mocker() def test_get_campaign(self, m): - json_response = test_data.get_campaign_json_response + json_response = test_newmode_data.get_campaign_json_response tbl = Table([json_response]) m.post(V2_API_AUTH_URL, json={"access_token": "fakeAccessToken"}) m.get( @@ -194,7 +194,7 @@ def test_get_campaign(self, m): @requests_mock.Mocker() def test_get_campaign_ids(self, m): lst = ["testCampaingID"] - json_response = test_data.get_campaign_ids_json_response + json_response = test_newmode_data.get_campaign_ids_json_response m.post(V2_API_AUTH_URL, json={"access_token": "fakeAccessToken"}) m.get(f"{V2_API_CAMPAIGNS_URL}jsonapi/node/action", json=json_response) assert_matching_tables(self.nm.get_campaign_ids(), lst) @@ -202,7 +202,7 @@ def test_get_campaign_ids(self, m): @requests_mock.Mocker() def test_get_recipient(self, m): self.city = "Vancouver" - json_response = test_data.get_recipient_json_response + json_response = test_newmode_data.get_recipient_json_response tbl = Table([json_response]) m.post(V2_API_AUTH_URL, json={"access_token": "fakeAccessToken"}) @@ -213,7 +213,7 @@ def test_get_recipient(self, m): @requests_mock.Mocker() def test_run_submit(self, m): - json_response = test_data.run_submit_json_response + json_response = test_newmode_data.run_submit_json_response json_input = { "action_id": self.campaign_id, "first_name": "TestFirstName", diff --git a/test/test_newmode/test_data.py b/test/test_newmode/test_newmode_data.py similarity index 100% rename from test/test_newmode/test_data.py rename to test/test_newmode/test_newmode_data.py From 3521c0878bbd1688a8df128ad8623fca14ddfff4 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Mon, 6 Jan 2025 16:36:10 -0800 Subject: [PATCH 18/30] typo --- parsons/newmode/newmode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index e9d7cbc27b..1ab0eca7a7 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -603,12 +603,12 @@ def __new__( """ api_version = check_env.check("NEWMODE_API_VERSION", api_version) if api_version.startswith("v2"): - if not client_id or client_secret: + if not (client_id and client_secret): raise ValueError("Missing client_id and/or client_secret") return NewmodeV2( client_id=client_id, client_secret=client_secret, api_version=api_version ) else: - if not api_user or api_password: + if not (api_user and api_password): raise ValueError("Missing api_user and/or api_password") return NewmodeV1(api_user=api_user, api_password=api_password, api_version=api_version) From d92fdc04e97bc2c2cba2f72a6aaea87c4cda381e Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Mon, 6 Jan 2025 16:39:11 -0800 Subject: [PATCH 19/30] typo --- parsons/newmode/newmode.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index 1ab0eca7a7..e5e35efc2a 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -603,12 +603,16 @@ def __new__( """ api_version = check_env.check("NEWMODE_API_VERSION", api_version) if api_version.startswith("v2"): - if not (client_id and client_secret): - raise ValueError("Missing client_id and/or client_secret") + if not client_id: + raise ValueError("Missing client_id") + if not client_secret: + raise ValueError("Missing client_secret") return NewmodeV2( client_id=client_id, client_secret=client_secret, api_version=api_version ) else: - if not (api_user and api_password): - raise ValueError("Missing api_user and/or api_password") + if not api_user: + raise ValueError("Missing api_user") + if not api_password: + raise ValueError("Missing api_password") return NewmodeV1(api_user=api_user, api_password=api_password, api_version=api_version) From d38077edb9005bfd7eb9f641e7e265502a3af579 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Mon, 6 Jan 2025 16:39:27 -0800 Subject: [PATCH 20/30] typo --- parsons/newmode/newmode.py | 1 - 1 file changed, 1 deletion(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index e5e35efc2a..826d50cbb6 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -365,7 +365,6 @@ def __init__( def base_request( self, method, - url, endpoint, data=None, json=None, From 234f05819e3dd36f5bebc1ce090bc3283939741d Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Mon, 6 Jan 2025 16:42:47 -0800 Subject: [PATCH 21/30] typo --- parsons/newmode/newmode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index 826d50cbb6..e7a1592410 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -432,7 +432,7 @@ def converted_request( endpoint=endpoint, ) if convert_to_table: - return OAuth2APIConnector.convert_to_table(response) + return OAuth2APIConnector.convert_to_table(data=response) else: return response From 3ad19f1a4a2fe0fe3d5050537a267dcdd3c9f6aa Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Mon, 6 Jan 2025 16:44:48 -0800 Subject: [PATCH 22/30] typo --- parsons/newmode/newmode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index e7a1592410..88140c0454 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -432,7 +432,7 @@ def converted_request( endpoint=endpoint, ) if convert_to_table: - return OAuth2APIConnector.convert_to_table(data=response) + return self.client.convert_to_table(data=response) else: return response From 4cc54c0771c677fd3be48374d1dc789515effddc Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Mon, 6 Jan 2025 16:56:06 -0800 Subject: [PATCH 23/30] add validation --- parsons/newmode/newmode.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index 88140c0454..501f1379e3 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -36,8 +36,8 @@ def __init__(self, api_user=None, api_password=None, api_version=None): logger.warning( "Newmode V1 API will be sunset in Feburary 2025. To use V2, set api_version=v2.1" ) - self.api_user = check_env.check("NEWMODE_API_USER", api_user) - self.api_password = check_env.check("NEWMODE_API_PASSWORD", api_password) + self.api_user = api_user + self.api_password = api_password self.api_version = api_version self.client = Client(api_user, api_password, api_version) @@ -358,8 +358,8 @@ def __init__( """ self.api_version = api_version self.base_url = V2_API_URL - self.client_id = check_env.check("NEWMODE_API_CLIENT_ID", client_id) - self.client_secret = check_env.check("NEWMODE_API_CLIENT_SECRET", client_secret) + self.client_id = client_id + self.client_secret = client_secret self.headers = {"content-type": "application/json"} def base_request( @@ -601,17 +601,15 @@ def __new__( NewMode Class """ api_version = check_env.check("NEWMODE_API_VERSION", api_version) + client_id = check_env.check("NEWMODE_API_CLIENT_ID", client_id) + client_secret = check_env.check("NEWMODE_API_CLIENT_SECRET", client_secret) + api_user = check_env.check("NEWMODE_API_USER", api_user) + api_password = check_env.check("NEWMODE_API_PASSWORD", api_password) + if api_version.startswith("v2"): - if not client_id: - raise ValueError("Missing client_id") - if not client_secret: - raise ValueError("Missing client_secret") return NewmodeV2( client_id=client_id, client_secret=client_secret, api_version=api_version ) - else: - if not api_user: - raise ValueError("Missing api_user") - if not api_password: - raise ValueError("Missing api_password") + if api_version.startswith("v1"): return NewmodeV1(api_user=api_user, api_password=api_password, api_version=api_version) + raise ValueError(f"{api_version} not supported.") From c689a0cd2eba293800b27140f1f666856f59ee21 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Mon, 6 Jan 2025 16:58:16 -0800 Subject: [PATCH 24/30] revert --- parsons/newmode/newmode.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index 501f1379e3..446e61a477 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -36,8 +36,8 @@ def __init__(self, api_user=None, api_password=None, api_version=None): logger.warning( "Newmode V1 API will be sunset in Feburary 2025. To use V2, set api_version=v2.1" ) - self.api_user = api_user - self.api_password = api_password + self.api_user = check_env.check("NEWMODE_API_USER", api_user) + self.api_password = check_env.check("NEWMODE_API_PASSWORD", api_password) self.api_version = api_version self.client = Client(api_user, api_password, api_version) @@ -358,8 +358,8 @@ def __init__( """ self.api_version = api_version self.base_url = V2_API_URL - self.client_id = client_id - self.client_secret = client_secret + self.client_id = check_env.check("NEWMODE_API_CLIENT_ID", client_id) + self.client_secret = check_env.check("NEWMODE_API_CLIENT_SECRET", client_secret) self.headers = {"content-type": "application/json"} def base_request( @@ -601,11 +601,6 @@ def __new__( NewMode Class """ api_version = check_env.check("NEWMODE_API_VERSION", api_version) - client_id = check_env.check("NEWMODE_API_CLIENT_ID", client_id) - client_secret = check_env.check("NEWMODE_API_CLIENT_SECRET", client_secret) - api_user = check_env.check("NEWMODE_API_USER", api_user) - api_password = check_env.check("NEWMODE_API_PASSWORD", api_password) - if api_version.startswith("v2"): return NewmodeV2( client_id=client_id, client_secret=client_secret, api_version=api_version From 63724a33f84f7d8757ad4d41034ae6cbe2274eac Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Mon, 6 Jan 2025 17:02:49 -0800 Subject: [PATCH 25/30] ruff --- parsons/utilities/api_connector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parsons/utilities/api_connector.py b/parsons/utilities/api_connector.py index 54a7479e9e..0f906f001d 100644 --- a/parsons/utilities/api_connector.py +++ b/parsons/utilities/api_connector.py @@ -316,4 +316,4 @@ def convert_to_table(self, data): else: table = Table([data]) - return table \ No newline at end of file + return table From fce0852886112eaea85c34dcdcdd1716ca69a488 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Wed, 15 Jan 2025 00:56:35 -0500 Subject: [PATCH 26/30] clean up --- parsons/newmode/newmode.py | 54 +++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index 446e61a477..f84dd83145 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -361,6 +361,15 @@ def __init__( self.client_id = check_env.check("NEWMODE_API_CLIENT_ID", client_id) self.client_secret = check_env.check("NEWMODE_API_CLIENT_SECRET", client_secret) self.headers = {"content-type": "application/json"} + self.default_client = OAuth2APIConnector( + uri=self.base_url, + auto_refresh_url=V2_API_AUTH_URL, + client_id=self.client_id, + client_secret=self.client_secret, + headers=self.headers, + token_url=V2_API_AUTH_URL, + grant_type="client_credentials", + ) def base_request( self, @@ -370,39 +379,23 @@ def base_request( json=None, data_key=None, params={}, - is_get_campaign_ids_method=False, supports_version=True, + client=None, + override_api_version=None, ): """ Internal method to instantiate OAuth2APIConnector class, make a call to Newmode API, and validate the response. """ - if is_get_campaign_ids_method: - base_uri = V2_API_CAMPAIGNS_URL - api_version = V2_API_CAMPAIGNS_VERSION - headers = V2_API_CAMPAIGNS_HEADERS - else: - base_uri = self.base_url - api_version = self.api_version - headers = self.headers - - self.client = OAuth2APIConnector( - uri=base_uri, - auto_refresh_url=V2_API_AUTH_URL, - client_id=self.client_id, - client_secret=self.client_secret, - headers=headers, - token_url=V2_API_AUTH_URL, - grant_type="client_credentials", - ) - + client = client if client else self.default_client + api_version = override_api_version if override_api_version else self.api_version url = f"{api_version}/{endpoint}" if supports_version else endpoint response = self.client.request( url=url, req_type=method, json=json, data=data, params=params ) response.raise_for_status() success_codes = [200, 201, 202, 204] - self.client.validate_response(response) + client.validate_response(response) if response.status_code in success_codes: response_json = response.json() if self.client.json_check(response) else None return response_json[data_key] if data_key and response_json else response_json @@ -418,7 +411,8 @@ def converted_request( params={}, convert_to_table=True, data_key=None, - is_get_campaign_ids_method=False, + client=None, + override_api_version=None, ): """Internal method to make a call to the Newmode API and convert the result to a Parsons table.""" response = self.base_request( @@ -427,9 +421,10 @@ def converted_request( data=data, params=params, data_key=data_key, - is_get_campaign_ids_method=is_get_campaign_ids_method, supports_version=supports_version, endpoint=endpoint, + client=client, + override_api_version=override_api_version, ) if convert_to_table: return self.client.convert_to_table(data=response) @@ -470,12 +465,23 @@ def get_campaign_ids(self, params={}): List containing all campaign ids. """ endpoint = "node/action" + campaigns_client = OAuth2APIConnector( + uri=V2_API_CAMPAIGNS_URL, + auto_refresh_url=V2_API_AUTH_URL, + client_id=self.client_id, + client_secret=self.client_secret, + headers=V2_API_CAMPAIGNS_HEADERS, + token_url=V2_API_AUTH_URL, + grant_type="client_credentials", + ) + data = self.converted_request( endpoint=endpoint, method="GET", params=params, data_key="data", - is_get_campaign_ids_method=True, + client=campaigns_client, + override_api_version=V2_API_CAMPAIGNS_VERSION, ) return data["id"] From d41c8154028122d0d1592f550140f369000c50e5 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Wed, 15 Jan 2025 01:01:54 -0500 Subject: [PATCH 27/30] more clean up --- parsons/newmode/newmode.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index f84dd83145..169cc6cec4 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -375,29 +375,28 @@ def base_request( self, method, endpoint, + client, data=None, json=None, data_key=None, params={}, supports_version=True, - client=None, override_api_version=None, ): """ Internal method to instantiate OAuth2APIConnector class, make a call to Newmode API, and validate the response. """ - client = client if client else self.default_client api_version = override_api_version if override_api_version else self.api_version url = f"{api_version}/{endpoint}" if supports_version else endpoint - response = self.client.request( + response = client.request( url=url, req_type=method, json=json, data=data, params=params ) response.raise_for_status() success_codes = [200, 201, 202, 204] client.validate_response(response) if response.status_code in success_codes: - response_json = response.json() if self.client.json_check(response) else None + response_json = response.json() if client.json_check(response) else None return response_json[data_key] if data_key and response_json else response_json raise Exception(f"API request encountered an error. Response: {response}") @@ -415,6 +414,8 @@ def converted_request( override_api_version=None, ): """Internal method to make a call to the Newmode API and convert the result to a Parsons table.""" + + client = client if client else self.default_client response = self.base_request( method=method, json=json, @@ -426,8 +427,9 @@ def converted_request( client=client, override_api_version=override_api_version, ) + if convert_to_table: - return self.client.convert_to_table(data=response) + return client.convert_to_table(data=response) else: return response From 15869717e7f6f87f811cf7435692f80b09a163b7 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Wed, 15 Jan 2025 01:05:03 -0500 Subject: [PATCH 28/30] clean up test --- test/test_newmode/test_newmode_data.py | 305 ++++++++++++++++--------- 1 file changed, 194 insertions(+), 111 deletions(-) diff --git a/test/test_newmode/test_newmode_data.py b/test/test_newmode/test_newmode_data.py index 65262d7ff2..a14cf7a7d1 100644 --- a/test/test_newmode/test_newmode_data.py +++ b/test/test_newmode/test_newmode_data.py @@ -1,122 +1,205 @@ get_campaign_json_response = { - "contact": { - "id": [{"value": 12345678}], - "uuid": [{"value": "test-uuid"}], - "revision_id": [{"value": 123456}], - "org_id": [{"value": 1234}], - "honorific": ["test"], - "first_name": [{"value": "TestFirstName"}], - "last_name": [{"value": "TestLastName"}], - "name_suffix": ["TestSuffix"], - "email": [{"value": "test_abc@test.com"}], - "mobile_phone": [], - "alternate_phone": [], - "twitter_handle": [], - "street_address": [], - "city": [], - "region": [], - "country": [], - "postal_code": [{"value": "test_postal_code"}], - "latitude": [], - "longitude": [], - "opt_in": [{"value": True}], - "nm_product_opt_in": [{"value": False}], - "nm_marketing_opt_in": [{"value": False}], - "groups": [ - { - "target_id": 1234, - "target_type": "contact_group", - "target_uuid": "test-uuid", - "url": "/contact-group/1234", - } - ], - "created": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], - "changed": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], - "prefill_hash": [{"value": "test-value"}], - "subscriber": [], - "sync_status": [[]], - "entitygroupfield": [ - { - "target_id": 1234567, - "target_type": "group_target", - "target_uuid": "test-value", - "url": "/group/1234/content/1234567", - } - ], + "langcode": "test", + "title": "test", + "field_call_introduction": "Hello [contact:first_name], thank you for participating in the [node:title] campaign.", + "field_call_voice": "english-us", + "field_communication_types": ["petition"], + "field_country": ["US"], + "field_datasets": [], + "field_email_from_supporter": False, + "field_email_replyto_supporter": True, + "field_embedded": False, + "field_embed_url": None, + "field_lookup_type": "none", + "field_message_editable": True, + "field_moderation": "off", + "field_org_id": "test-value", + "field_paused": False, + "field_randomize_targets": False, + "field_selected_targets": [], + "field_show_target_jurisdiction": False, + "field_show_target_name": False, + "field_show_target_party": False, + "field_show_target_position": False, + "field_symbolic_recipient": None, + "field_target_limit": 10, + "field_test_phone_number": None, + "field_thankyou_email_body": { + "value": "[contact:first_name], thank you for adding your voice in our teset campaign.\r\n\r\n\r\nLet’s amplify your voice. Can you take the next step by sharing this action with your friends and family now?\r\n[submission:share_links]\r\n\r\nThe more people that join this campaign, the stronger our impact will be.\r\n\r\n\r\nMore soon,\r\nYour Test Org team", + "processed": "

[contact:first_name], thank you for adding your voice in our teset campaign.

\n

Let’s amplify your voice. Can you take the next step by sharing this action with your friends and family now?
\n[submission:share_links]

\n

The more people that join this campaign, the stronger our impact will be.

\n

More soon,
\nYour Test Org team

\n", }, - "links": { - "Facebook": { - "label": "Share on Facebook", - "url": "https://www.facebook.com/sharer.php?s=100&u=https://win.newmode.net/test", - "title": "", + "field_thankyou_email_disable": False, + "field_thankyou_email_subject": "Here's your next step [contact:first_name]", + "field_thankyou_from_name": "test", + "field_thankyou_redirect": None, + "field_thankyou_type": "nm", + "draft": False, + "archived": False, + "message": [], + "buttons": { + "Facebook": {"label": "Share on Facebook", "url": "", "title": None}, + "Twitter": {"label": "Tweet to your followers", "url": "", "title": None}, + "Email": {"label": "Send an email", "url": "", "title": "Add your voice to this campaign!"}, + "Copy Link": {"label": "Copy Link", "url": "", "title": None}, + }, + "description": "
We demand that [What your decision makers should do]
", + "primary_image": "https://nwmd.social/sites/default/files/defaultSocialImg/Headersection_2.jpg", + "form": { + "header": { + "name": "header", + "type": "header", + "builder": {"type": "header", "label": "Header", "remove": False}, }, - "Twitter": { - "label": "Tweet to your followers", - "url": "https://nwmd.social/s/twitter/test", - "title": "", + "container2": { + "name": "container2", + "type": "group", + "addClass": "two-column-layout", + "schema": { + "column1": { + "name": "column1", + "type": "group", + "addClass": "content-column", + "columns": {"container": 8}, + "schema": { + "image": { + "name": "image", + "type": "image", + "builder": {"name": "image", "type": "image", "remove": True}, + }, + "recipient": { + "name": "recipient", + "type": "recipient", + "builder": {"type": "recipient", "label": "recipient", "remove": False}, + }, + "description": { + "name": "description", + "type": "description", + "builder": { + "type": "description", + "label": "Paragraph", + "remove": False, + }, + }, + }, + }, + "column2": { + "name": "column2", + "type": "group", + "addClass": "form-column", + "columns": {"container": 4}, + "schema": { + "goalmeter": { + "name": "goalmeter", + "type": "goal-meter", + "builder": {"type": "goalmeter"}, + }, + "first_name": { + "name": "first_name", + "placeholder": "First name", + "type": "text", + "rules": ["required"], + "builder": {"type": "first_name", "remove": False}, + }, + "last_name": { + "name": "last_name", + "placeholder": "Last name", + "type": "text", + "rules": ["required"], + "builder": {"type": "last_name", "remove": False}, + }, + "email": { + "name": "email", + "placeholder": "Email address", + "type": "text", + "inputType": "email", + "rules": ["nullable", "email"], + "builder": {"type": "email", "remove": False}, + }, + "lookup": { + "name": "lookup", + "placeholder": "Address", + "type": "lookup", + "builder": {"type": "lookup", "remove": False}, + }, + "opt-in": { + "name": "opt-in", + "type": "opt-in", + "label": None, + "builder": {"type": "optin", "remove": False}, + "optintype": "statement", + "optintext": "Yes please.", + "optouttext": "No thankyou.", + "optinprivacy": "Test Org will protect your privacy.", + }, + "submit": { + "name": "submit", + "type": "button", + "buttonLabel": "Sign this petition now", + "submits": True, + "builder": {"type": "submit", "remove": False}, + }, + "disclaimer": { + "name": "disclaimer", + "type": "disclaimer", + "builder": { + "type": "disclaimer", + "label": "New/Mode Opt In", + "remove": False, + }, + }, + }, + }, + }, + "builder": {"type": "container2", "label": "2 columns"}, }, - "Email": { - "label": "Send an email", - "url": "https://nwmd.social/s/email/test", - "title": "Add your voice to this campaign!", + }, + "thank_you": { + "header": { + "name": "header", + "type": "header", + "builder": {"type": "header", "label": "Header"}, }, - "Copy Link": { - "label": "Copy Link", - "url": "https://nwmd.social/s/copylink/test", - "title": "", + "share_buttons": { + "name": "sharebuttons", + "type": "share-buttons", + "builder": {"type": "sharebuttons", "remove": False}, }, }, - "message": "Already submitted", - "submission": { - "sid": [{"value": 123456}], - "uuid": [{"value": "test-value"}], - "revision_id": [{"value": 123456}], - "action_id": [ - { - "target_id": 1234, - "target_type": "node", - "target_uuid": "test-value", - "url": "/node/1234", - } - ], - "contact_id": [ - { - "target_id": 1234567, - "target_type": "contact", - "target_uuid": "test-value", - "url": "/contact/1234567", - } - ], - "status": [ - { - "target_id": 12, - "target_type": "test-value", - "target_uuid": "test-value", - "url": "/taxonomy/term/12", - } - ], - "testmode": [{"value": False}], - "edited": [{"value": True}], - "device": [], - "browser": [], - "browser_version": [], - "os": [], - "os_version": [], - "parent_url": [], - "source_code": [], - "search_value": [], - "created": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], - "changed": [{"value": "0123456789", "format": "Y-m-d\\TH:i:sP"}], - "entitygroupfield": [ - { - "target_id": 12345678, - "target_type": "group_content", - "target_uuid": "test-value", - "url": "test-url", - } - ], + "talking_points": { + "header": { + "name": "header", + "type": "header", + "builder": {"type": "header", "label": "Header"}, + }, + "info": { + "name": "info", + "type": "static", + "tag": "p", + "content": "You will receive a call shortly connecting you with {count} recipients", + "builder": {"type": "p", "label": "Paragraph"}, + }, + "talking_points": { + "name": "talking_points", + "type": "static", + "tag": "p", + "content": '
\n
\n Date: Wed, 15 Jan 2025 01:06:54 -0500 Subject: [PATCH 29/30] ruff --- parsons/newmode/newmode.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index 169cc6cec4..1dba7faa7c 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -389,9 +389,7 @@ def base_request( """ api_version = override_api_version if override_api_version else self.api_version url = f"{api_version}/{endpoint}" if supports_version else endpoint - response = client.request( - url=url, req_type=method, json=json, data=data, params=params - ) + response = client.request(url=url, req_type=method, json=json, data=data, params=params) response.raise_for_status() success_codes = [200, 201, 202, 204] client.validate_response(response) From de745c76eff2f21266615f25e91b1de2947b9695 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Mon, 27 Jan 2025 14:51:39 -0500 Subject: [PATCH 30/30] minor adjustments --- parsons/newmode/newmode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index 1dba7faa7c..8ec2af7919 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -34,7 +34,7 @@ def __init__(self, api_user=None, api_password=None, api_version=None): Newmode class """ logger.warning( - "Newmode V1 API will be sunset in Feburary 2025. To use V2, set api_version=v2.1" + "Newmode V1 API will be sunset in Feburary 28th, 2025. To use V2, set api_version=v2.1" ) self.api_user = check_env.check("NEWMODE_API_USER", api_user) self.api_password = check_env.check("NEWMODE_API_PASSWORD", api_password) @@ -44,7 +44,7 @@ def __init__(self, api_user=None, api_password=None, api_version=None): def convert_to_table(self, data): # Internal method to create a Parsons table from a data element. table = None - if type(data) is list: + if isinstance(data, list): table = Table(data) else: table = Table([data])