From 8dfc53dc6a1b9f9ddb37f06604eef93c22d1a562 Mon Sep 17 00:00:00 2001 From: Konrad Schlatte Date: Fri, 16 Dec 2022 11:38:57 +0000 Subject: [PATCH] Source Retently - Add Feedback, NPS Score, Campaigns streams (#19381) * add sources * add campaigns * change parse_response * spec.json * schema and spec * feedback schema * docker file version --- .../connectors/source-retently/Dockerfile | 2 +- .../integration_tests/configured_catalog.json | 40 +++++++++++- .../source_retently/schemas/campaigns.json | 13 ++++ .../source_retently/schemas/feedback.json | 48 +++++++++++++++ .../source_retently/schemas/nps.json | 12 ++++ .../source-retently/source_retently/source.py | 61 ++++++++++++++++--- .../source-retently/source_retently/spec.json | 14 ++--- .../source-retently/unit_tests/test_source.py | 6 +- 8 files changed, 174 insertions(+), 22 deletions(-) create mode 100644 airbyte-integrations/connectors/source-retently/source_retently/schemas/campaigns.json create mode 100644 airbyte-integrations/connectors/source-retently/source_retently/schemas/feedback.json create mode 100644 airbyte-integrations/connectors/source-retently/source_retently/schemas/nps.json diff --git a/airbyte-integrations/connectors/source-retently/Dockerfile b/airbyte-integrations/connectors/source-retently/Dockerfile index 7ccaa4606ff3b..7544772929e5e 100644 --- a/airbyte-integrations/connectors/source-retently/Dockerfile +++ b/airbyte-integrations/connectors/source-retently/Dockerfile @@ -34,5 +34,5 @@ COPY source_retently ./source_retently ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.2 +LABEL io.airbyte.version=0.1.3 LABEL io.airbyte.name=airbyte/source-retently diff --git a/airbyte-integrations/connectors/source-retently/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-retently/integration_tests/configured_catalog.json index 8d19736aadcca..23552e0aa88c9 100644 --- a/airbyte-integrations/connectors/source-retently/integration_tests/configured_catalog.json +++ b/airbyte-integrations/connectors/source-retently/integration_tests/configured_catalog.json @@ -14,7 +14,7 @@ }, { "stream": { - "name": "companies", + "name": "reports", "json_schema": { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object" @@ -26,7 +26,43 @@ }, { "stream": { - "name": "reports", + "name": "nps", + "json_schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object" + }, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "feedback", + "json_schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object" + }, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "campaigns", + "json_schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object" + }, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "companies", "json_schema": { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object" diff --git a/airbyte-integrations/connectors/source-retently/source_retently/schemas/campaigns.json b/airbyte-integrations/connectors/source-retently/source_retently/schemas/campaigns.json new file mode 100644 index 0000000000000..26d6e832dfeae --- /dev/null +++ b/airbyte-integrations/connectors/source-retently/source_retently/schemas/campaigns.json @@ -0,0 +1,13 @@ +{ + "type": "object", + "properties": + { + "id": { "type": "string" }, + "name": { "type": "string" }, + "isActive": { "type": "boolean" }, + "templateId": { "type": "string" }, + "metric": { "type": "string" }, + "type": { "type": "string" }, + "channel": { "type": "string" } + } +} diff --git a/airbyte-integrations/connectors/source-retently/source_retently/schemas/feedback.json b/airbyte-integrations/connectors/source-retently/source_retently/schemas/feedback.json new file mode 100644 index 0000000000000..298ea540872b8 --- /dev/null +++ b/airbyte-integrations/connectors/source-retently/source_retently/schemas/feedback.json @@ -0,0 +1,48 @@ +{ + "type": "object", + "properties": { + "id": { "type": "string" }, + "customerId": { "type": "string" }, + "email": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "companyName": { "type": "string" }, + "jobTitle": { "type": "string" }, + "country": { "type": "string" }, + "state": { "type": "string" }, + "city": { "type": "string" }, + "tags": { + "type": "array", + "items": { "type": "string" } + }, + "customProps": { + "type": "array", + "items": { "type": "object" } + }, + "campaignId": { "type": "string" }, + "campaignName": { "type": "string" }, + "createdDate": { "type": "string" }, + "score": { "type": "number" }, + "comment": { "type": "string" }, + "checkbox": { "type": "boolean" }, + "additionalQuestions": { + "type": "array", + "items": { "type": "object" } + }, + "feedbackTags": { + "type": "array", + "items": { "type": "string" } + }, + "notes": { + "type": "array", + "items": { "type": "object" } + }, + "status": { "type": "string" }, + "assigned": { "type": "string" }, + "ratingCategory": { "type": "string" }, + "resolved": { "type": "boolean" }, + "channel": { "type": "string" }, + "metricsType": { "type": "string" }, + "isBogus": { "type": "boolean" } + } +} diff --git a/airbyte-integrations/connectors/source-retently/source_retently/schemas/nps.json b/airbyte-integrations/connectors/source-retently/source_retently/schemas/nps.json new file mode 100644 index 0000000000000..1a0616ec7db4d --- /dev/null +++ b/airbyte-integrations/connectors/source-retently/source_retently/schemas/nps.json @@ -0,0 +1,12 @@ +{ + "score": { "type": "integer" }, + "scoreSum": { "type": "integer" }, + "metricsType": { "type": "string" }, + "promoters": { "type": "integer" }, + "passives": { "type": "integer" }, + "detractors": { "type": "integer" }, + "promotersCount": { "type": "integer" }, + "passivesCount": { "type": "integer" }, + "detractorsCount": { "type": "integer" }, + "totalResponses": { "type": "integer" } +} diff --git a/airbyte-integrations/connectors/source-retently/source_retently/source.py b/airbyte-integrations/connectors/source-retently/source_retently/source.py index ea9b3d5962e81..e42beff2779ed 100644 --- a/airbyte-integrations/connectors/source-retently/source_retently/source.py +++ b/airbyte-integrations/connectors/source-retently/source_retently/source.py @@ -35,7 +35,7 @@ def get_authenticator(config): def check_connection(self, logger, config) -> Tuple[bool, any]: try: auth = self.get_authenticator(config) - stream = Companies(auth) + stream = Customers(auth) records = stream.read_records(sync_mode=SyncMode.full_refresh) next(records) return True, None @@ -44,7 +44,7 @@ def check_connection(self, logger, config) -> Tuple[bool, any]: def streams(self, config: Mapping[str, Any]) -> List[Stream]: auth = self.get_authenticator(config) - return [Customers(auth), Companies(auth), Reports(auth)] + return [Customers(auth), Companies(auth), Reports(auth), Nps(auth), Campaigns(auth), Feedback(auth)] class RetentlyStream(HttpStream): @@ -67,11 +67,9 @@ def parse_response( next_page_token: Mapping[str, Any] = None, ) -> Iterable[Mapping]: data = response.json().get("data") - if data: - stream_data = data.get(self.json_path) if self.json_path else data - if stream_data: - for d in stream_data: - yield d + stream_data = data.get(self.json_path) if self.json_path else data + for d in stream_data: + yield d def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]: json = response.json().get("data", dict()) @@ -118,7 +116,56 @@ def path( self, stream_state: Mapping[str, Any] = None, stream_slice: Mapping[str, Any] = None, next_page_token: Mapping[str, Any] = None ) -> str: return "reports" + # does not support pagination + def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]: + return None + +class Nps(RetentlyStream): + json_path = None + + def path( + self, stream_state: Mapping[str, Any] = None, stream_slice: Mapping[str, Any] = None, next_page_token: Mapping[str, Any] = None + ) -> str: + return "nps/score" # does not support pagination def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]: return None + + def parse_response( + self, + response: requests.Response, + stream_state: Mapping[str, Any], + stream_slice: Mapping[str, Any] = None, + next_page_token: Mapping[str, Any] = None, + ) -> Iterable[Mapping]: + data = response.json().get("data") + yield data + +class Campaigns(RetentlyStream): + json_path = "campaigns" + + def path( + self, stream_state: Mapping[str, Any] = None, stream_slice: Mapping[str, Any] = None, next_page_token: Mapping[str, Any] = None + ) -> str: + return "campaigns" + + def parse_response( + self, + response: requests.Response, + stream_state: Mapping[str, Any], + stream_slice: Mapping[str, Any] = None, + next_page_token: Mapping[str, Any] = None, + ) -> Iterable[Mapping]: + data = response.json() + stream_data = data.get(self.json_path) if self.json_path else data + for d in stream_data: + yield d + +class Feedback(RetentlyStream): + json_path = "responses" + + def path( + self, stream_state: Mapping[str, Any] = None, stream_slice: Mapping[str, Any] = None, next_page_token: Mapping[str, Any] = None + ) -> str: + return "feedback" diff --git a/airbyte-integrations/connectors/source-retently/source_retently/spec.json b/airbyte-integrations/connectors/source-retently/source_retently/spec.json index a509dc6c58db5..59b7f3408017c 100644 --- a/airbyte-integrations/connectors/source-retently/source_retently/spec.json +++ b/airbyte-integrations/connectors/source-retently/source_retently/spec.json @@ -15,13 +15,11 @@ "type": "object", "title": "Authenticate via Retently (OAuth)", "required": ["client_id", "client_secret", "refresh_token"], - "additionalProperties": false, + "additionalProperties": true, "properties": { "auth_type": { "type": "string", "const": "Client", - "enum": ["Client"], - "default": "Client", "order": 0 }, "client_id": { @@ -47,13 +45,11 @@ "type": "object", "title": "Authenticate with API Token", "required": ["api_key"], - "additionalProperties": false, + "additionalProperties": true, "properties": { "auth_type": { "type": "string", "const": "Token", - "enum": ["Token"], - "default": "Token", "order": 0 }, "api_key": { @@ -75,7 +71,7 @@ "oauth_config_specification": { "complete_oauth_output_specification": { "type": "object", - "additionalProperties": false, + "additionalProperties": true, "properties": { "refresh_token": { "type": "string", @@ -85,7 +81,7 @@ }, "complete_oauth_server_input_specification": { "type": "object", - "additionalProperties": false, + "additionalProperties": true, "properties": { "client_id": { "type": "string" @@ -97,7 +93,7 @@ }, "complete_oauth_server_output_specification": { "type": "object", - "additionalProperties": false, + "additionalProperties": true, "properties": { "client_id": { "type": "string", diff --git a/airbyte-integrations/connectors/source-retently/unit_tests/test_source.py b/airbyte-integrations/connectors/source-retently/unit_tests/test_source.py index 67b822eab1e04..18bf1bd5c4c96 100644 --- a/airbyte-integrations/connectors/source-retently/unit_tests/test_source.py +++ b/airbyte-integrations/connectors/source-retently/unit_tests/test_source.py @@ -11,8 +11,8 @@ def setup_responses(): responses.add( responses.GET, - "https://app.retently.com/api/v2/companies", - json={"data": {"companies": [{}]}}, + "https://app.retently.com/api/v2/nps/customers", + json={"data": {"subscribers": [{}]}}, ) @@ -28,5 +28,5 @@ def test_streams(mocker): source = SourceRetently() config_mock = MagicMock() streams = source.streams(config_mock) - expected_streams_number = 3 + expected_streams_number = 6 assert len(streams) == expected_streams_number