Skip to content

Commit b60a7b1

Browse files
roman-yermilov-gloctavia-squidington-iii
authored andcommitted
Source Freshdesk: add availability strategy (airbytehq#22145)
* Source FreshDesk: start using availability strategy * Source FreshDesk: fix flake issues * Source Freshdesk: update version * auto-bump connector version --------- Co-authored-by: Octavia Squidington III <[email protected]>
1 parent e8ceb89 commit b60a7b1

File tree

9 files changed

+71
-71
lines changed

9 files changed

+71
-71
lines changed

airbyte-config/init/src/main/resources/seed/source_definitions.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,11 +568,14 @@
568568
- name: Freshdesk
569569
sourceDefinitionId: ec4b9503-13cb-48ab-a4ab-6ade4be46567
570570
dockerRepository: airbyte/source-freshdesk
571-
dockerImageTag: 3.0.0
571+
dockerImageTag: 3.0.2
572572
documentationUrl: https://docs.airbyte.com/integrations/sources/freshdesk
573573
icon: freshdesk.svg
574574
sourceType: api
575575
releaseStage: generally_available
576+
allowedHosts:
577+
hosts:
578+
- "*.freshdesk.com"
576579
- name: Freshsales
577580
sourceDefinitionId: eca08d79-7b92-4065-b7f3-79c14836ebe7
578581
dockerRepository: airbyte/source-freshsales

airbyte-config/init/src/main/resources/seed/source_specs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4514,7 +4514,7 @@
45144514
supportsNormalization: false
45154515
supportsDBT: false
45164516
supported_destination_sync_modes: []
4517-
- dockerImage: "airbyte/source-freshdesk:3.0.0"
4517+
- dockerImage: "airbyte/source-freshdesk:3.0.2"
45184518
spec:
45194519
documentationUrl: "https://docs.airbyte.com/integrations/sources/freshdesk"
45204520
connectionSpecification:

airbyte-integrations/connectors/source-freshdesk/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,5 @@ COPY source_freshdesk ./source_freshdesk
3434
ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py"
3535
ENTRYPOINT ["python", "/airbyte/integration_code/main.py"]
3636

37-
LABEL io.airbyte.version=3.0.0
37+
LABEL io.airbyte.version=3.0.2
3838
LABEL io.airbyte.name=airbyte/source-freshdesk
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#
2+
# Copyright (c) 2022 Airbyte, Inc., all rights reserved.
3+
#
4+
5+
import requests
6+
from airbyte_cdk.sources.streams.http.availability_strategy import HttpAvailabilityStrategy
7+
8+
9+
class FreshdeskAvailabilityStrategy(HttpAvailabilityStrategy):
10+
def reasons_for_unavailable_status_codes(self, stream, logger, source, error):
11+
unauthorized_error_message = f"The endpoint to access stream '{stream.name}' returned 401: Unauthorized. "
12+
unauthorized_error_message += "This is most likely due to wrong credentials. "
13+
unauthorized_error_message += self._visit_docs_message(logger, source)
14+
15+
reasons = super(FreshdeskAvailabilityStrategy, self).reasons_for_unavailable_status_codes(stream, logger, source, error)
16+
reasons[requests.codes.UNAUTHORIZED] = unauthorized_error_message
17+
18+
return reasons

airbyte-integrations/connectors/source-freshdesk/source_freshdesk/source.py

Lines changed: 38 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33
#
44

55
import logging
6-
from typing import Any, List, Mapping, Optional, Tuple
7-
from urllib.parse import urljoin
6+
from typing import Any, List, Mapping
87

9-
import requests
108
from airbyte_cdk.sources import AbstractSource
9+
from airbyte_cdk.sources.declarative.checks import CheckStream
1110
from airbyte_cdk.sources.streams import Stream
1211
from requests.auth import HTTPBasicAuth
1312
from source_freshdesk.streams import (
@@ -52,53 +51,45 @@ def __init__(self, api_key: str) -> None:
5251

5352

5453
class SourceFreshdesk(AbstractSource):
55-
def check_connection(self, logger: logging.Logger, config: Mapping[str, Any]) -> Tuple[bool, Optional[Any]]:
56-
alive = True
57-
error_msg = None
54+
@staticmethod
55+
def _get_stream_kwargs(config: Mapping[str, Any]) -> dict:
56+
return {"authenticator": FreshdeskAuth(config["api_key"]), "config": config}
57+
58+
def check_connection(self, logger: logging.Logger, config: Mapping[str, Any]):
5859
try:
59-
url = urljoin(f"https://{config['domain'].rstrip('/')}", "/api/v2/settings/helpdesk")
60-
response = requests.get(url=url, auth=FreshdeskAuth(config["api_key"]))
61-
response.raise_for_status()
62-
except requests.HTTPError as error:
63-
alive = False
64-
body = error.response.json()
65-
error_msg = f"{body.get('code')}: {body.get('message')}"
60+
check_stream = CheckStream(stream_names=["settings"], options={})
61+
return check_stream.check_connection(self, logger, config)
6662
except Exception as error:
67-
alive = False
68-
error_msg = repr(error)
69-
70-
return alive, error_msg
63+
return False, repr(error)
7164

7265
def streams(self, config: Mapping[str, Any]) -> List[Stream]:
73-
authenticator = FreshdeskAuth(config["api_key"])
74-
stream_kwargs = {"authenticator": authenticator, "config": config}
7566
return [
76-
Agents(**stream_kwargs),
77-
BusinessHours(**stream_kwargs),
78-
CannedResponseFolders(**stream_kwargs),
79-
CannedResponses(**stream_kwargs),
80-
Companies(**stream_kwargs),
81-
Contacts(**stream_kwargs),
82-
Conversations(**stream_kwargs),
83-
DiscussionCategories(**stream_kwargs),
84-
DiscussionComments(**stream_kwargs),
85-
DiscussionForums(**stream_kwargs),
86-
DiscussionTopics(**stream_kwargs),
87-
EmailConfigs(**stream_kwargs),
88-
EmailMailboxes(**stream_kwargs),
89-
Groups(**stream_kwargs),
90-
Products(**stream_kwargs),
91-
Roles(**stream_kwargs),
92-
ScenarioAutomations(**stream_kwargs),
93-
Settings(**stream_kwargs),
94-
Skills(**stream_kwargs),
95-
SlaPolicies(**stream_kwargs),
96-
SolutionArticles(**stream_kwargs),
97-
SolutionCategories(**stream_kwargs),
98-
SolutionFolders(**stream_kwargs),
99-
TimeEntries(**stream_kwargs),
100-
TicketFields(**stream_kwargs),
101-
Tickets(**stream_kwargs),
102-
SatisfactionRatings(**stream_kwargs),
103-
Surveys(**stream_kwargs),
67+
Agents(**self._get_stream_kwargs(config)),
68+
BusinessHours(**self._get_stream_kwargs(config)),
69+
CannedResponseFolders(**self._get_stream_kwargs(config)),
70+
CannedResponses(**self._get_stream_kwargs(config)),
71+
Companies(**self._get_stream_kwargs(config)),
72+
Contacts(**self._get_stream_kwargs(config)),
73+
Conversations(**self._get_stream_kwargs(config)),
74+
DiscussionCategories(**self._get_stream_kwargs(config)),
75+
DiscussionComments(**self._get_stream_kwargs(config)),
76+
DiscussionForums(**self._get_stream_kwargs(config)),
77+
DiscussionTopics(**self._get_stream_kwargs(config)),
78+
EmailConfigs(**self._get_stream_kwargs(config)),
79+
EmailMailboxes(**self._get_stream_kwargs(config)),
80+
Groups(**self._get_stream_kwargs(config)),
81+
Products(**self._get_stream_kwargs(config)),
82+
Roles(**self._get_stream_kwargs(config)),
83+
ScenarioAutomations(**self._get_stream_kwargs(config)),
84+
Settings(**self._get_stream_kwargs(config)),
85+
Skills(**self._get_stream_kwargs(config)),
86+
SlaPolicies(**self._get_stream_kwargs(config)),
87+
SolutionArticles(**self._get_stream_kwargs(config)),
88+
SolutionCategories(**self._get_stream_kwargs(config)),
89+
SolutionFolders(**self._get_stream_kwargs(config)),
90+
TimeEntries(**self._get_stream_kwargs(config)),
91+
TicketFields(**self._get_stream_kwargs(config)),
92+
Tickets(**self._get_stream_kwargs(config)),
93+
SatisfactionRatings(**self._get_stream_kwargs(config)),
94+
Surveys(**self._get_stream_kwargs(config)),
10495
]

airbyte-integrations/connectors/source-freshdesk/source_freshdesk/streams.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from airbyte_cdk.sources.streams.http import HttpStream, HttpSubStream
1717
from airbyte_cdk.sources.utils.transform import TransformConfig, TypeTransformer
1818
from requests.auth import AuthBase
19+
from source_freshdesk.availability_strategy import FreshdeskAvailabilityStrategy
1920
from source_freshdesk.utils import CallCredit
2021

2122

@@ -48,20 +49,13 @@ def url_base(self) -> str:
4849
return parse.urljoin(f"https://{self.domain.rstrip('/')}", "/api/v2/")
4950

5051
@property
51-
def availability_strategy(self) -> Optional["AvailabilityStrategy"]:
52-
return None
52+
def availability_strategy(self) -> Optional[AvailabilityStrategy]:
53+
return FreshdeskAvailabilityStrategy()
5354

5455
def backoff_time(self, response: requests.Response) -> Optional[float]:
5556
if response.status_code == requests.codes.too_many_requests:
5657
return float(response.headers.get("Retry-After", 0))
5758

58-
def should_retry(self, response: requests.Response) -> bool:
59-
if response.status_code == requests.codes.FORBIDDEN:
60-
self.forbidden_stream = True
61-
setattr(self, "raise_on_http_errors", False)
62-
self.logger.warn(f"Stream `{self.name}` is not available. {response.text}")
63-
return super().should_retry(response)
64-
6559
def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]:
6660
link_header = response.headers.get("Link")
6761
if not link_header:

airbyte-integrations/connectors/source-freshdesk/unit_tests/test_source.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ def test_check_connection_invalid_api_key(requests_mock, config):
2626
requests_mock.register_uri("GET", "/api/v2/settings/helpdesk", responses)
2727
ok, error_msg = SourceFreshdesk().check_connection(logger, config=config)
2828

29-
assert not ok and error_msg == "invalid_credentials: You have to be logged in to perform this action."
29+
assert not ok and error_msg == "The endpoint to access stream \'settings\' returned 401: Unauthorized. " \
30+
"This is most likely due to wrong credentials. " \
31+
"Please visit https://docs.airbyte.com/integrations/sources/freshdesk to learn more. " \
32+
"You have to be logged in to perform this action."
3033

3134

3235
def test_check_connection_empty_config(config):
@@ -45,7 +48,7 @@ def test_check_connection_invalid_config(config):
4548
assert not ok and error_msg
4649

4750

48-
def test_check_connection_exception(config):
51+
def test_check_connection_exception(requests_mock, config):
4952
ok, error_msg = SourceFreshdesk().check_connection(logger, config=config)
5053

5154
assert not ok and error_msg

airbyte-integrations/connectors/source-freshdesk/unit_tests/test_streams.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -209,13 +209,3 @@ def test_full_refresh_discussion_comments(requests_mock, authenticator, config):
209209
records = _read_full_refresh(stream)
210210

211211
assert len(records) == 120
212-
213-
214-
def test_403_skipped(requests_mock, authenticator, config):
215-
# this case should neither raise an error nor retry
216-
requests_mock.register_uri("GET", "/api/v2/tickets", json=[{"id": 1705, "updated_at": "2022-05-05T00:00:00Z"}])
217-
requests_mock.register_uri("GET", "/api/v2/tickets/1705/conversations", status_code=403)
218-
stream = Conversations(authenticator=authenticator, config=config)
219-
records = _read_full_refresh(stream)
220-
assert records == []
221-
assert len(requests_mock.request_history) == 2

docs/integrations/sources/freshdesk.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ The Freshdesk connector should not run into Freshdesk API limitations under norm
6767

6868
| Version | Date | Pull Request | Subject |
6969
|:--------|:-----------|:---------------------------------------------------------|:--------------------------------------------------------------------------------------|
70+
| 3.0.2 | 2023-02-06 | [21970](https://github.com/airbytehq/airbyte/pull/21970) | Enable availability strategy for all streams |
7071
| 3.0.0 | 2023-01-31 | [22164](https://github.com/airbytehq/airbyte/pull/22164) | Rename nested `business_hours` table to `working_hours` |
7172
| 2.0.1 | 2023-01-27 | [21888](https://github.com/airbytehq/airbyte/pull/21888) | Set `AvailabilityStrategy` for streams explicitly to `None` |
7273
| 2.0.0 | 2022-12-20 | [20416](https://github.com/airbytehq/airbyte/pull/20416) | Fix `SlaPolicies` stream schema |

0 commit comments

Comments
 (0)