Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 0 additions & 128 deletions airbyte-integrations/connectors/source-freshdesk/components.py

This file was deleted.

17 changes: 11 additions & 6 deletions airbyte-integrations/connectors/source-freshdesk/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1370,8 +1370,7 @@ definitions:
type: SimpleRetriever
requester:
$ref: "#/definitions/base_requester"
type: CustomRequester
class_name: source_declarative_manifest.components.FreshdeskTicketsIncrementalRequester
class_name: SimpleRetriever
path: tickets
http_method: GET
request_parameters:
Expand All @@ -1383,6 +1382,11 @@ definitions:
error_handlers:
- type: DefaultErrorHandler
response_filters:
- type: HttpResponseFilter
action: RESET_PAGINATION
http_codes:
- 400
error_message_contains: You cannot access tickets beyond the 300th page
- type: HttpResponseFilter
action: FAIL
http_codes:
Expand All @@ -1409,13 +1413,14 @@ definitions:
field_name: page
inject_into: request_parameter
pagination_strategy:
type: CustomPaginationStrategy
type: PageIncrement
page_size: 100
class_name: source_declarative_manifest.components.FreshdeskTicketsPaginationStrategy
start_from_page: 1
pagination_reset:
type: PaginationReset
action: SPLIT_USING_CURSOR
incremental_sync:
type: CustomIncrementalSync
class_name: source_declarative_manifest.components.FreshdeskTicketsIncrementalSync
type: DatetimeBasedCursor
cursor_field: updated_at
start_datetime:
type: MinMaxDatetime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ data:
connectorSubtype: api
connectorType: source
definitionId: ec4b9503-13cb-48ab-a4ab-6ade4be46567
dockerImageTag: 3.2.0-rc.1
dockerImageTag: 3.2.1
dockerRepository: airbyte/source-freshdesk
documentationUrl: https://docs.airbyte.com/integrations/sources/freshdesk
githubIssueLabel: source-freshdesk
Expand All @@ -29,7 +29,7 @@ data:
enabled: true
releases:
rolloutConfiguration:
enableProgressiveRollout: true
enableProgressiveRollout: false
releaseStage: generally_available
supportLevel: certified
tags:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from datetime import datetime
from typing import Any, Mapping


class ConfigBuilder:
def __init__(self) -> None:
self._config = {
"api_key": "fake_api_key",
"domain": "any-domain.freshdesk.com",
"start_date": "2010-01-18T21:18:20Z",
}

def start_date(self, start_date: datetime) -> "ConfigBuilder":
self._config["start_date"] = start_date.strftime("%Y-%m-%dT%H:%M:%SZ")
return self

def api_key(self, api_key: str) -> "ConfigBuilder":
self._config["api_key"] = api_key
return self

def domain(self, domain: str) -> "ConfigBuilder":
self._config["domain"] = domain
return self

def build(self) -> Mapping[str, Any]:
return self._config
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Copyright (c) 2024 Airbyte, Inc., all rights reserved.

import sys
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, Optional
from unittest import TestCase

from airbyte_cdk import ConfiguredAirbyteCatalog, SyncMode, TState, YamlDeclarativeSource
from airbyte_cdk.test.catalog_builder import CatalogBuilder
from airbyte_cdk.test.entrypoint_wrapper import EntrypointOutput
from airbyte_cdk.test.entrypoint_wrapper import read as entrypoint_read
from airbyte_cdk.test.mock_http import HttpMocker, HttpRequest, HttpResponse
from airbyte_cdk.test.mock_http.response_builder import (
FieldPath,
HttpResponseBuilder,
RecordBuilder,
create_record_builder,
create_response_builder,
find_template, RootPath,
)
from airbyte_cdk.test.state_builder import StateBuilder

from config_builder import ConfigBuilder

PAGE_LIMIT_300TH_REACHED = '{"description":"Validation failed","errors":[{"field":"page","message":"You cannot access tickets beyond the 300th page. Please provide a smaller page number.","code":"invalid_value"}]}'


def _get_manifest_path() -> Path:
source_declarative_manifest_path = Path("/airbyte/integration_code/source_declarative_manifest")
if source_declarative_manifest_path.exists():
return source_declarative_manifest_path
return Path(__file__).parent.parent.parent


_SOURCE_FOLDER_PATH = _get_manifest_path()
_YAML_FILE_PATH = _SOURCE_FOLDER_PATH / "manifest.yaml"

sys.path.append(str(_SOURCE_FOLDER_PATH)) # to allow loading custom components

_DOMAIN = "a-domain.freshdesk.com"
_API_KEY = "an_api_key"

def _catalog() -> ConfiguredAirbyteCatalog:
return CatalogBuilder().with_stream("tickets", SyncMode.full_refresh).build()


def _source(catalog: ConfiguredAirbyteCatalog, config: Dict[str, Any], state: Optional[TState]) -> YamlDeclarativeSource:
return YamlDeclarativeSource(path_to_yaml=str(_YAML_FILE_PATH), catalog=catalog, config=config, state=state)


def _response_template() -> Dict[str, Any]:
return find_template("tickets", __file__)


def _record() -> RecordBuilder:
return create_record_builder(
_response_template(), RootPath(), record_id_path=FieldPath("id"), record_cursor_path=FieldPath("updated_at")
)


def _response() -> HttpResponseBuilder:
return create_response_builder(
response_template=_response_template(),
records_path=RootPath(),
)


def read(
config_builder: Optional[ConfigBuilder] = None,
state_builder: Optional[StateBuilder] = None,
expecting_exception: bool = False,
) -> EntrypointOutput:
catalog = _catalog()
config = config_builder.build() if config_builder else ConfigBuilder().build()
state = state_builder.build() if state_builder else StateBuilder().build()
return entrypoint_read(_source(catalog, config, state), config, catalog, state, expecting_exception)


class TicketsTest(TestCase):
@HttpMocker()
def test_when_read_then_extract_records(self, http_mocker: HttpMocker) -> None:
http_mocker.get(
HttpRequest(f"https://{_DOMAIN}/api/v2/tickets?order_type=asc&order_by=updated_at&include=description,requester,stats&per_page=100&updated_since=2022-01-01T00%3A00%3A00Z"),
_response().with_record(_record()).with_record(_record()).build(),
)
output = read(ConfigBuilder().domain(_DOMAIN).start_date(datetime(2022, 1, 1)), StateBuilder())
assert len(output.records) == 2

@HttpMocker()
def test_given_hitting_300th_page_when_read_then_reset_pagination(self, http_mocker: HttpMocker) -> None:
http_mocker.get(
HttpRequest(f"https://{_DOMAIN}/api/v2/tickets?order_type=asc&order_by=updated_at&include=description,requester,stats&per_page=100&updated_since=2022-01-01T00%3A00%3A00Z"),
self._a_response_with_full_page("2023-01-01T00:00:00Z"),
)
for page in range(2, 301):
http_mocker.get(
HttpRequest(f"https://{_DOMAIN}/api/v2/tickets?order_type=asc&order_by=updated_at&include=description,requester,stats&page={page}&per_page=100&updated_since=2022-01-01T00%3A00%3A00Z"),
self._a_response_with_full_page("2023-01-01T00:00:00Z"),
)
http_mocker.get(
HttpRequest(f"https://{_DOMAIN}/api/v2/tickets?order_type=asc&order_by=updated_at&include=description,requester,stats&page=301&per_page=100&updated_since=2022-01-01T00%3A00%3A00Z"),
HttpResponse(PAGE_LIMIT_300TH_REACHED, 400),
)

http_mocker.get(
HttpRequest(f"https://{_DOMAIN}/api/v2/tickets?order_type=asc&order_by=updated_at&include=description,requester,stats&per_page=100&updated_since=2023-01-01T00%3A00%3A00Z"),
_response().with_record(_record()).with_record(_record()).build(),
)

output = read(ConfigBuilder().domain(_DOMAIN).start_date(datetime(2022, 1, 1)), StateBuilder())

assert len(output.records) == 300 * 100 + 2

def _a_response_with_full_page(self, cursor_value: str) -> HttpResponse:
response = _response()
for x in range(100):
response.with_record(_record().with_cursor(cursor_value))
return response.build()
Loading
Loading