-
Notifications
You must be signed in to change notification settings - Fork 4.6k
✨ Source Zendesk Support: add tests and clean-up #55676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,7 +32,6 @@ | |
{"stream": "satisfaction_ratings", "data": {"url": "https://d3v-airbyte.zendesk.com/api/v2/satisfaction_ratings/7235633102607.json", "id": 7235633102607, "assignee_id": null, "group_id": null, "requester_id": 361089721035, "ticket_id": 146, "score": "offered", "created_at": "2023-06-19T18:01:40Z", "updated_at": "2023-06-19T18:01:40Z", "comment": null}, "emitted_at": 1720179592962} | ||
{"stream": "satisfaction_ratings", "data": {"url": "https://d3v-airbyte.zendesk.com/api/v2/satisfaction_ratings/8178725484175.json", "id": 8178725484175, "assignee_id": null, "group_id": null, "requester_id": 8178212241935, "ticket_id": 158, "score": "offered", "created_at": "2023-10-20T12:01:58Z", "updated_at": "2023-10-20T12:01:58Z", "comment": null}, "emitted_at": 1720179592971} | ||
{"stream": "satisfaction_ratings", "data": {"url": "https://d3v-airbyte.zendesk.com/api/v2/satisfaction_ratings/9862120719631.json", "id": 9862120719631, "assignee_id": null, "group_id": null, "requester_id": 9861847678735, "ticket_id": 161, "score": "offered", "created_at": "2024-05-28T21:01:33Z", "updated_at": "2024-05-28T21:01:33Z", "comment": null}, "emitted_at": 1720179592979} | ||
{"stream":"ticket_activities","data":{"url":"https://d3v-airbyte.zendesk.com/api/v2/activities/11836244663055.json","id":11836244663055,"title":"Danylo commented on ticket #160: I hope so!.","verb":"tickets.comment","user_id":360786799676,"actor_id":9515132940047,"updated_at":"2025-01-29T18:21:22Z","created_at":"2025-01-29T18:21:22Z","object":{"comment":{"value":"I hope so!","public":true}},"target":{"ticket":{"id":160,"subject":"Stream filling request"}},"user":{"id":360786799676,"url":"https://d3v-airbyte.zendesk.com/api/v2/users/360786799676.json","name":"Team Airbyte","email":"[email protected]","created_at":"2020-11-17T23:55:24Z","updated_at":"2025-01-31T21:22:08Z","time_zone":"Pacific/Noumea","iana_time_zone":"Pacific/Noumea","phone":null,"shared_phone_number":null,"photo":{"url":"https://d3v-airbyte.zendesk.com/api/v2/attachments/7282857066895.json","id":7282857066895,"file_name":"Airbyte_logo_220x220.png","content_url":"https://d3v-airbyte.zendesk.com/system/photos/7282857066895/Airbyte_logo_220x220.png","mapped_content_url":"https://d3v-airbyte.zendesk.com/system/photos/7282857066895/Airbyte_logo_220x220.png","content_type":"image/png","size":5442,"width":80,"height":80,"inline":false,"deleted":false,"thumbnails":[{"url":"https://d3v-airbyte.zendesk.com/api/v2/attachments/7282824912911.json","id":7282824912911,"file_name":"Airbyte_logo_220x220_thumb.png","content_url":"https://d3v-airbyte.zendesk.com/system/photos/7282857066895/Airbyte_logo_220x220_thumb.png","mapped_content_url":"https://d3v-airbyte.zendesk.com/system/photos/7282857066895/Airbyte_logo_220x220_thumb.png","content_type":"image/png","size":1422,"width":32,"height":32,"inline":false,"deleted":false}]},"locale_id":1,"locale":"en-US","organization_id":360033549136,"role":"admin","verified":true,"external_id":null,"tags":[],"alias":"Team Airbyte","active":true,"shared":false,"shared_agent":false,"last_login_at":"2025-01-31T21:22:08Z","two_factor_auth_enabled":null,"signature":null,"details":null,"notes":null,"role_type":4,"custom_role_id":360006308896,"moderator":true,"ticket_restriction":null,"only_private_comments":false,"restricted_agent":false,"suspended":false,"default_group_id":360003074836,"report_csv":true,"user_fields":{"test_display_name_checkbox_field":false,"test_display_name_decimal_field":null,"test_display_name_text_field":null}},"actor":{"id":9515132940047,"url":"https://d3v-airbyte.zendesk.com/api/v2/users/9515132940047.json","name":"Danylo","email":"[email protected]","created_at":"2024-04-12T13:38:07Z","updated_at":"2024-04-12T13:38:07Z","time_zone":"Pacific/Noumea","iana_time_zone":"Pacific/Noumea","phone":null,"shared_phone_number":null,"photo":null,"locale_id":1,"locale":"en-US","organization_id":null,"role":"end-user","verified":false,"external_id":null,"tags":[],"alias":"","active":true,"shared":false,"shared_agent":false,"last_login_at":null,"two_factor_auth_enabled":null,"signature":null,"details":"","notes":"","role_type":null,"custom_role_id":null,"moderator":false,"ticket_restriction":"requested","only_private_comments":false,"restricted_agent":true,"suspended":false,"default_group_id":null,"report_csv":false,"user_fields":{"test_display_name_checkbox_field":false,"test_display_name_decimal_field":null,"test_display_name_text_field":null}}},"emitted_at":1738602991286} | ||
{"stream": "ticket_audits", "data": {"id": 10021116193295, "ticket_id": 160, "created_at": "2024-06-19T09:49:54Z", "author_id": 9515132940047, "metadata": {"system": {"message_id": "<[email protected]>", "client": "Microsoft Outlook 16.0", "email_id": "01J0QYE6SGCX3Z936BFETHRR8P", "ip_address": "024.06.19.02", "raw_email_identifier": "10414779/4fe5f3c6-857a-4560-a065-aae562a36b53.eml", "json_email_identifier": "10414779/4fe5f3c6-857a-4560-a065-aae562a36b53.json", "eml_redacted": false, "location": "Mountain View, CA, United States", "latitude": 37.3859, "longitude": -122.0882}, "custom": {}, "flags": [15], "flags_options": {"15": {"trusted": true}}, "trusted": true, "suspension_type_id": null}, "events": [{"id": 10021099820047, "type": "Comment", "author_id": 9515132940047, "body": "\n\n\n\nI hope so!", "html_body": "<div class=\"zd-comment zd-comment-pre-styled\" dir=\"auto\"><div style=\"page: WordSection1;\"><p style=\"font-size: 11.0pt; margin: 0in 0in .0001pt;\" dir=\"auto\"> </p><p style=\"font-size: 11.0pt; margin: 0in 0in .0001pt;\" dir=\"auto\"> </p><p style=\"font-size: 11.0pt; margin: 0in 0in .0001pt;\" dir=\"auto\">I hope so!</p><p style=\"font-size: 11.0pt; margin: 0in 0in .0001pt;\" dir=\"auto\"> </p></div></div>", "plain_body": " I hope so! ", "public": true, "attachments": [], "audit_id": 10021116193295}, {"id": 10021116193423, "type": "Notification", "via": {"channel": "rule", "source": {"from": {"deleted": false, "title": "Notify assignee of comment update", "id": 360011363236, "revision_id": 1}, "rel": "trigger"}}, "subject": "[{{ticket.account}}] Re: {{ticket.title}}", "body": "This ticket (#{{ticket.id}}) has been updated.\n\n{{ticket.comments_formatted}}", "recipients": [360786799676]}], "via": {"channel": "email", "source": {"from": {"address": "[email protected]", "name": "Danylo", "original_recipients": ["[email protected]", "[email protected]"]}, "to": {"name": "Airbyte", "address": "[email protected]"}, "rel": null}}}, "emitted_at": 1720179595459} | ||
{"stream": "ticket_audits", "data": {"id": 10020996855439, "ticket_id": 160, "created_at": "2024-06-19T09:28:21Z", "author_id": 360786799676, "metadata": {"system": {"client": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", "ip_address": "45.89.90.157", "location": "Lviv, 46, Ukraine", "latitude": 49.839, "longitude": 24.0191}, "custom": {}}, "events": [{"id": 10020996855567, "type": "Change", "value": "high", "field_name": "priority", "previous_value": "normal"}], "via": {"channel": "web", "source": {"from": {}, "to": {}, "rel": null}}}, "emitted_at": 1720179595472} | ||
{"stream": "ticket_audits", "data": {"id": 10020982311311, "ticket_id": 160, "created_at": "2024-06-19T09:27:57Z", "author_id": 360786799676, "metadata": {"system": {"client": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", "ip_address": "45.89.90.157", "location": "Lviv, 46, Ukraine", "latitude": 49.839, "longitude": 24.0191}, "custom": {}}, "events": [{"id": 10020982311439, "type": "Change", "value": "normal", "field_name": "priority", "previous_value": "high"}], "via": {"channel": "web", "source": {"from": {}, "to": {}, "rel": null}}}, "emitted_at": 1720179595483} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,7 @@ requires = [ "poetry-core>=1.0.0",] | |
build-backend = "poetry.core.masonry.api" | ||
|
||
[tool.poetry] | ||
version = "4.7.1" | ||
version = "4.7.2" | ||
name = "source-zendesk-support" | ||
description = "Source implementation for Zendesk Support." | ||
authors = [ "Airbyte <[email protected]>",] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -261,7 +261,7 @@ def get_stream_state_value(self, stream_state: Mapping[str, Any] = None) -> int: | |
Returns the state value, if exists. Otherwise, returns user defined `Start Date`. | ||
""" | ||
state = stream_state.get(self.cursor_field) or self._start_date if stream_state else self._start_date | ||
return calendar.timegm(pendulum.parse(state).utctimetuple()) | ||
return int(state) if isinstance(state, int) or state.isdigit() else calendar.timegm(pendulum.parse(state).utctimetuple()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't have an issue w/ the code, but i'm interested in how this got surfaced. Was there a customer that saw this as an issue or was this just found when you were writing mock server tests? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is not a bug. It is to have this test pass. However, there are so few users in prod that I don't think we intend to revert. We could just remove that honestly... |
||
|
||
|
||
class CursorPaginationZendeskSupportStream(IncrementalZendeskSupportStream): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
# Copyright (c) 2023 Airbyte, Inc., all rights reserved. | ||
|
||
from datetime import datetime, timezone | ||
from multiprocessing.context import AuthenticationError | ||
from unittest import TestCase | ||
|
||
import freezegun | ||
import pendulum | ||
|
||
from airbyte_cdk.models import AirbyteStateBlob, SyncMode | ||
from airbyte_cdk.test.mock_http import HttpMocker, HttpRequest | ||
from airbyte_cdk.test.state_builder import StateBuilder | ||
|
||
from .config import ConfigBuilder | ||
from .utils import datetime_to_string, read_stream | ||
from .zs_requests import PostsRequestBuilder | ||
from .zs_requests.request_authenticators import ApiTokenAuthenticator | ||
from .zs_requests.request_authenticators.authenticator import Authenticator | ||
from .zs_responses import PostsResponseBuilder | ||
from .zs_responses.records import PostsRecordBuilder | ||
|
||
|
||
_NOW = datetime.now(timezone.utc) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe a dumb question, but why do we need to use two different libraries to get are we be unable to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know but I think that our reasoning should be to move away from pendulum because it's not supported anymore. I haven't done so because there are a lot of utils relying on it. I wanted to use Devin and try to ask it to move remove all the usages of pendulum eventually. I kicked off the job but didn't check the result yet... |
||
_START_DATE = pendulum.now(tz="UTC").subtract(years=2) | ||
|
||
|
||
@freezegun.freeze_time(_NOW.isoformat()) | ||
class TestPostsStream(TestCase): | ||
def _config(self) -> ConfigBuilder: | ||
return ( | ||
ConfigBuilder() | ||
.with_basic_auth_credentials("[email protected]", "password") | ||
.with_subdomain("d3v-airbyte") | ||
.with_start_date(pendulum.now(tz="UTC").subtract(years=2)) | ||
) | ||
|
||
def _get_authenticator(self, config): | ||
return ApiTokenAuthenticator(email=config["credentials"]["email"], password=config["credentials"]["api_token"]) | ||
|
||
def _base_posts_request(self, authenticator: Authenticator) -> PostsRequestBuilder: | ||
return PostsRequestBuilder.posts_endpoint(authenticator).with_page_size(100) | ||
|
||
@HttpMocker() | ||
def test_given_one_page_when_read_then_return_records(self, http_mocker): | ||
config = self._config().with_start_date(_START_DATE).build() | ||
api_token_authenticator = self._get_authenticator(config) | ||
http_mocker.get( | ||
self._base_posts_request(api_token_authenticator).with_start_time(datetime_to_string(_START_DATE)).build(), | ||
PostsResponseBuilder.posts_response() | ||
.with_record(PostsRecordBuilder.posts_record()) | ||
.with_record(PostsRecordBuilder.posts_record()) | ||
.build(), | ||
) | ||
|
||
output = read_stream("posts", SyncMode.full_refresh, config) | ||
|
||
assert len(output.records) == 2 | ||
|
||
@HttpMocker() | ||
def test_given_has_more_when_read_then_paginate(self, http_mocker): | ||
config = self._config().with_start_date(_START_DATE).build() | ||
api_token_authenticator = self._get_authenticator(config) | ||
http_mocker.get( | ||
self._base_posts_request(api_token_authenticator).with_start_time(datetime_to_string(_START_DATE)).build(), | ||
PostsResponseBuilder.posts_response(self._base_posts_request(api_token_authenticator).build()) | ||
.with_record(PostsRecordBuilder.posts_record()) | ||
.with_record(PostsRecordBuilder.posts_record()) | ||
.with_pagination() | ||
.build(), | ||
) | ||
http_mocker.get( | ||
self._base_posts_request(api_token_authenticator).with_after_cursor("after-cursor").build(), | ||
PostsResponseBuilder.posts_response().with_record(PostsRecordBuilder.posts_record()).build(), | ||
) | ||
|
||
output = read_stream("posts", SyncMode.full_refresh, config) | ||
|
||
assert len(output.records) == 3 | ||
|
||
@HttpMocker() | ||
def test_given_ignore_pagination_when_read_then_do_not_paginate(self, http_mocker): | ||
""" | ||
Given the db query `select * from actor where actor_definition_id = '79c1aa37-dae3-42ae-b333-d1c105477715' and configuration->>'ignore_pagination' = 'true' and tombstone = false;`, we can see that some connections are using this config. I don't exactly understand the use for that but here is the test since this is used. | ||
""" | ||
config = self._config().with_start_date(_START_DATE).with_ignore_pagination().build() | ||
api_token_authenticator = self._get_authenticator(config) | ||
http_mocker.get( | ||
self._base_posts_request(api_token_authenticator).with_start_time(datetime_to_string(_START_DATE)).build(), | ||
PostsResponseBuilder.posts_response(self._base_posts_request(api_token_authenticator).build()) | ||
.with_record(PostsRecordBuilder.posts_record()) | ||
.with_record(PostsRecordBuilder.posts_record()) | ||
.with_pagination() | ||
.build(), | ||
) | ||
|
||
output = read_stream("posts", SyncMode.full_refresh, config) | ||
|
||
assert len(output.records) == 2 | ||
assert len(output.errors) == 0 | ||
|
||
@HttpMocker() | ||
def test_when_read_then_set_state_value_to_most_recent_cursor_value(self, http_mocker): | ||
config = self._config().with_start_date(_START_DATE).build() | ||
api_token_authenticator = self._get_authenticator(config) | ||
most_recent_cursor_value = datetime_to_string(_START_DATE.add(days=2)) | ||
http_mocker.get( | ||
PostsRequestBuilder.posts_endpoint(api_token_authenticator) | ||
.with_start_time(datetime_to_string(_START_DATE)) | ||
.with_page_size(100) | ||
.build(), | ||
PostsResponseBuilder.posts_response() | ||
.with_record(PostsRecordBuilder.posts_record().with_cursor(most_recent_cursor_value)) | ||
.with_record(PostsRecordBuilder.posts_record().with_cursor(datetime_to_string(_START_DATE.add(days=1)))) | ||
.build(), | ||
) | ||
|
||
output = read_stream("posts", SyncMode.full_refresh, config) | ||
|
||
assert output.most_recent_state.stream_state == AirbyteStateBlob({"updated_at": most_recent_cursor_value}) | ||
|
||
@HttpMocker() | ||
def test_given_input_state_when_read_then_set_state_value_to_most_recent_cursor_value(self, http_mocker): | ||
config = self._config().with_start_date(_START_DATE).build() | ||
api_token_authenticator = self._get_authenticator(config) | ||
state_cursor_value = datetime_to_string(_START_DATE.add(days=2)) | ||
http_mocker.get( | ||
PostsRequestBuilder.posts_endpoint(api_token_authenticator).with_start_time(state_cursor_value).with_page_size(100).build(), | ||
PostsResponseBuilder.posts_response().with_record(PostsRecordBuilder.posts_record()).build(), | ||
) | ||
|
||
output = read_stream( | ||
"posts", SyncMode.full_refresh, config, StateBuilder().with_stream_state("posts", {"updated_at": state_cursor_value}).build() | ||
) | ||
|
||
assert len(output.records) == 1 | ||
|
||
@HttpMocker() | ||
def test_given_input_state_with_low_code_format_when_read_then_set_state_value_to_most_recent_cursor_value(self, http_mocker): | ||
config = self._config().with_start_date(_START_DATE).build() | ||
api_token_authenticator = self._get_authenticator(config) | ||
state_cursor_value = _START_DATE.add(days=2) | ||
http_mocker.get( | ||
PostsRequestBuilder.posts_endpoint(api_token_authenticator) | ||
.with_start_time(datetime_to_string(state_cursor_value)) | ||
.with_page_size(100) | ||
.build(), | ||
PostsResponseBuilder.posts_response().with_record(PostsRecordBuilder.posts_record()).build(), | ||
) | ||
|
||
output = read_stream( | ||
"posts", | ||
SyncMode.full_refresh, | ||
config, | ||
StateBuilder().with_stream_state("posts", {"updated_at": str(state_cursor_value.int_timestamp)}).build(), | ||
) | ||
|
||
assert len(output.records) == 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on https://connectors.airbyte.com/files/generated_reports/test_summary/source-zendesk-support/index.html, this is failing for a week now. The retention period is 30 days which means we would need to create some data every 30 days for this stream. I prefer just disabling it for now