From 596a4367afc5a01b94b043406d2ceb149e82cffb Mon Sep 17 00:00:00 2001 From: Denys Davydov Date: Thu, 22 Sep 2022 14:27:28 +0300 Subject: [PATCH] Source salesforce: handle japanese characters (#17001) * #454 oncall source salesforce: handle japanese characters * source salesforce: upd changelog * source salesforce: flake fix * #454 source salesforce: adjust public interface to CDK, do not take into account state when choosing the API type * auto-bump connector version [ci skip] Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 2 +- .../connectors/source-salesforce/Dockerfile | 2 +- .../source_salesforce/source.py | 32 +++---- .../source_salesforce/streams.py | 12 +-- .../source-salesforce/unit_tests/api_test.py | 46 ++-------- ...figured_catalog.json => bulk_catalog.json} | 0 .../source-salesforce/unit_tests/conftest.py | 21 ++++- .../unit_tests/discovery_test.py | 6 -- .../unit_tests/rest_catalog.json | 28 ++++++ .../unit_tests/test_memory.py | 6 -- docs/integrations/sources/salesforce.md | 85 ++++++++++--------- 12 files changed, 120 insertions(+), 122 deletions(-) rename airbyte-integrations/connectors/source-salesforce/unit_tests/{configured_catalog.json => bulk_catalog.json} (100%) create mode 100644 airbyte-integrations/connectors/source-salesforce/unit_tests/rest_catalog.json diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 09bae2809a4a..22a30733a935 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -908,7 +908,7 @@ - name: Salesforce sourceDefinitionId: b117307c-14b6-41aa-9422-947e34922962 dockerRepository: airbyte/source-salesforce - dockerImageTag: 1.0.15 + dockerImageTag: 1.0.16 documentationUrl: https://docs.airbyte.io/integrations/sources/salesforce icon: salesforce.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 1663f61d6bd8..61a86a086a7c 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -9379,7 +9379,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-salesforce:1.0.15" +- dockerImage: "airbyte/source-salesforce:1.0.16" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/salesforce" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-salesforce/Dockerfile b/airbyte-integrations/connectors/source-salesforce/Dockerfile index ed3b77b4dbda..3c12790a7004 100644 --- a/airbyte-integrations/connectors/source-salesforce/Dockerfile +++ b/airbyte-integrations/connectors/source-salesforce/Dockerfile @@ -13,5 +13,5 @@ RUN pip install . ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=1.0.15 +LABEL io.airbyte.version=1.0.16 LABEL io.airbyte.name=airbyte/source-salesforce diff --git a/airbyte-integrations/connectors/source-salesforce/source_salesforce/source.py b/airbyte-integrations/connectors/source-salesforce/source_salesforce/source.py index 2f997425fa21..36de182cb431 100644 --- a/airbyte-integrations/connectors/source-salesforce/source_salesforce/source.py +++ b/airbyte-integrations/connectors/source-salesforce/source_salesforce/source.py @@ -2,12 +2,12 @@ # Copyright (c) 2022 Airbyte, Inc., all rights reserved. # -import copy -from typing import Any, Iterator, List, Mapping, MutableMapping, Optional, Tuple +from typing import Any, Iterator, List, Mapping, MutableMapping, Optional, Tuple, Union from airbyte_cdk import AirbyteLogger -from airbyte_cdk.models import AirbyteMessage, ConfiguredAirbyteCatalog +from airbyte_cdk.models import AirbyteMessage, AirbyteStateMessage, ConfiguredAirbyteCatalog from airbyte_cdk.sources import AbstractSource +from airbyte_cdk.sources.connector_state_manager import ConnectorStateManager from airbyte_cdk.sources.streams import Stream from airbyte_cdk.sources.streams.http.auth import TokenAuthenticator from airbyte_cdk.sources.utils.schema_helpers import split_config @@ -37,7 +37,7 @@ def check_connection(self, logger: AirbyteLogger, config: Mapping[str, Any]) -> return True, None @classmethod - def _get_api_type(cls, stream_name, properties, stream_state): + def _get_api_type(cls, stream_name, properties): # Salesforce BULK API currently does not support loading fields with data type base64 and compound data properties_not_supported_by_bulk = { key: value for key, value in properties.items() if value.get("format") == "base64" or "object" in value["type"] @@ -49,12 +49,10 @@ def _get_api_type(cls, stream_name, properties, stream_state): # For such cases connector tries to use BULK API because it uses POST request and passes properties in the request body. bulk_required = properties_length + 2000 > Salesforce.REQUEST_SIZE_LIMITS - if bulk_required and not rest_required: - return "bulk" - elif rest_required and not bulk_required: + if rest_required and not bulk_required: return "rest" - elif not bulk_required and not rest_required: - return "rest" if stream_state else "bulk" + if not rest_required: + return "bulk" @classmethod def generate_streams( @@ -62,7 +60,6 @@ def generate_streams( config: Mapping[str, Any], stream_objects: Mapping[str, Any], sf_object: Salesforce, - state: Mapping[str, Any] = None, ) -> List[Stream]: """ "Generates a list of stream by their names. It can be used for different tests too""" authenticator = TokenAuthenticator(sf_object.access_token) @@ -70,10 +67,9 @@ def generate_streams( streams = [] for stream_name, sobject_options in stream_objects.items(): streams_kwargs = {"sobject_options": sobject_options} - stream_state = state.get(stream_name, {}) if state else {} selected_properties = stream_properties.get(stream_name, {}).get("properties", {}) - api_type = cls._get_api_type(stream_name, selected_properties, stream_state) + api_type = cls._get_api_type(stream_name, selected_properties) if api_type == "rest": full_refresh, incremental = SalesforceStream, IncrementalSalesforceStream elif api_type == "bulk": @@ -91,10 +87,10 @@ def generate_streams( return streams - def streams(self, config: Mapping[str, Any], catalog: ConfiguredAirbyteCatalog = None, state: Mapping[str, Any] = None) -> List[Stream]: + def streams(self, config: Mapping[str, Any], catalog: ConfiguredAirbyteCatalog = None) -> List[Stream]: sf = self._get_sf_object(config) stream_objects = sf.get_validated_streams(config=config, catalog=catalog) - streams = self.generate_streams(config, stream_objects, sf, state=state) + streams = self.generate_streams(config, stream_objects, sf) streams.append(Describe(sf_api=sf, catalog=catalog)) return streams @@ -103,17 +99,17 @@ def read( logger: AirbyteLogger, config: Mapping[str, Any], catalog: ConfiguredAirbyteCatalog, - state: Optional[MutableMapping[str, Any]] = None, + state: Union[List[AirbyteStateMessage], MutableMapping[str, Any]] = None, ) -> Iterator[AirbyteMessage]: """ Overwritten to dynamically receive only those streams that are necessary for reading for significant speed gains (Salesforce has a strict API limit on requests). """ - connector_state = copy.deepcopy(state or {}) config, internal_config = split_config(config) # get the streams once in case the connector needs to make any queries to generate them logger.info("Starting generating streams") - stream_instances = {s.name: s for s in self.streams(config, catalog=catalog, state=state)} + stream_instances = {s.name: s for s in self.streams(config, catalog=catalog)} + state_manager = ConnectorStateManager(stream_instance_map=stream_instances, state=state) logger.info(f"Starting syncing {self.name}") self._stream_to_instance_map = stream_instances for configured_stream in catalog.streams: @@ -128,7 +124,7 @@ def read( logger=logger, stream_instance=stream_instance, configured_stream=configured_stream, - connector_state=connector_state, + state_manager=state_manager, internal_config=internal_config, ) except exceptions.HTTPError as error: diff --git a/airbyte-integrations/connectors/source-salesforce/source_salesforce/streams.py b/airbyte-integrations/connectors/source-salesforce/source_salesforce/streams.py index 7234f0894a55..b3a936393863 100644 --- a/airbyte-integrations/connectors/source-salesforce/source_salesforce/streams.py +++ b/airbyte-integrations/connectors/source-salesforce/source_salesforce/streams.py @@ -272,13 +272,13 @@ def execute_job(self, query: str, url: str) -> Tuple[Optional[str], Optional[str return None, job_status return job_full_url, job_status - def filter_null_bytes(self, s: str): + def filter_null_bytes(self, b: bytes): """ https://github.com/airbytehq/airbyte/issues/8300 """ - res = s.replace("\x00", "") - if len(res) < len(s): - self.logger.warning("Filter 'null' bytes from string, size reduced %d -> %d chars", len(s), len(res)) + res = b.replace(b"\x00", b"") + if len(res) < len(b): + self.logger.warning("Filter 'null' bytes from string, size reduced %d -> %d chars", len(b), len(res)) return res def download_data(self, url: str, chunk_size: float = 1024) -> os.PathLike: @@ -292,9 +292,9 @@ def download_data(self, url: str, chunk_size: float = 1024) -> os.PathLike: # set filepath for binary data from response tmp_file = os.path.realpath(os.path.basename(url)) with closing(self._send_http_request("GET", f"{url}/results", stream=True)) as response: - with open(tmp_file, "w") as data_file: + with open(tmp_file, "wb") as data_file: for chunk in response.iter_content(chunk_size=chunk_size): - data_file.writelines(self.filter_null_bytes(self.decode(chunk))) + data_file.write(self.filter_null_bytes(chunk)) # check the file exists if os.path.isfile(tmp_file): return tmp_file diff --git a/airbyte-integrations/connectors/source-salesforce/unit_tests/api_test.py b/airbyte-integrations/connectors/source-salesforce/unit_tests/api_test.py index a868da2dd1ab..e4abedb9af27 100644 --- a/airbyte-integrations/connectors/source-salesforce/unit_tests/api_test.py +++ b/airbyte-integrations/connectors/source-salesforce/unit_tests/api_test.py @@ -24,12 +24,6 @@ ) -@pytest.fixture(autouse=True) -def time_sleep_mock(mocker): - time_mock = mocker.patch("time.sleep", lambda x: None) - yield time_mock - - def test_bulk_sync_creation_failed(stream_config, stream_api): stream: BulkIncrementalSalesforceStream = generate_stream("Account", stream_config, stream_api) with requests_mock.Mocker() as m: @@ -58,26 +52,6 @@ def test_stream_contains_unsupported_properties_by_bulk(stream_config, stream_ap assert not isinstance(stream, BulkSalesforceStream) -def test_stream_has_state_rest_api_should_be_used(stream_config, stream_api): - """ - Stream `ActiveFeatureLicenseMetric` has state, in that case REST API stream will be used for it. - """ - stream_name = "ActiveFeatureLicenseMetric" - state = {stream_name: {"SystemModstamp": "2122-08-22T05:08:29.000Z"}} - stream = generate_stream(stream_name, stream_config, stream_api, state=state) - assert not isinstance(stream, BulkSalesforceStream) - - -def test_stream_has_no_state_bulk_api_should_be_used(stream_config, stream_api): - """ - Stream `ActiveFeatureLicenseMetric` has no state, in that case BULK API stream will be used for it. - """ - stream_name = "ActiveFeatureLicenseMetric" - state = {"other_stream": {"SystemModstamp": "2122-08-22T05:08:29.000Z"}} - stream = generate_stream(stream_name, stream_config, stream_api, state=state) - assert isinstance(stream, BulkSalesforceStream) - - @pytest.mark.parametrize("item_number", [0, 15, 2000, 2324, 3000]) def test_bulk_sync_pagination(item_number, stream_config, stream_api): stream: BulkIncrementalSalesforceStream = generate_stream("Account", stream_config, stream_api) @@ -239,7 +213,7 @@ def configure_request_params_mock(stream_1, stream_2): stream_2.request_params.return_value = {"q": "query"} -def test_rate_limit_bulk(stream_config, stream_api, configured_catalog, state): +def test_rate_limit_bulk(stream_config, stream_api, bulk_catalog, state): """ Connector should stop the sync if one stream reached rate limit stream_1, stream_2, stream_3, ... @@ -282,7 +256,7 @@ def test_rate_limit_bulk(stream_config, stream_api, configured_catalog, state): m.register_uri("POST", stream.path(), creation_responses) - result = [i for i in source.read(logger=logger, config=stream_config, catalog=configured_catalog, state=state)] + result = [i for i in source.read(logger=logger, config=stream_config, catalog=bulk_catalog, state=state)] assert stream_1.request_params.called assert ( not stream_2.request_params.called @@ -295,7 +269,7 @@ def test_rate_limit_bulk(stream_config, stream_api, configured_catalog, state): assert state_record.state.data["Account"]["LastModifiedDate"] == "2021-11-05" # state checkpoint interval is 5. -def test_rate_limit_rest(stream_config, stream_api, configured_catalog, state): +def test_rate_limit_rest(stream_config, stream_api, rest_catalog, state): """ Connector should stop the sync if one stream reached rate limit stream_1, stream_2, stream_3, ... @@ -303,8 +277,8 @@ def test_rate_limit_rest(stream_config, stream_api, configured_catalog, state): Next streams should not be executed. """ - stream_1: IncrementalSalesforceStream = generate_stream("Account", stream_config, stream_api, state=state) - stream_2: IncrementalSalesforceStream = generate_stream("Asset", stream_config, stream_api, state=state) + stream_1: IncrementalSalesforceStream = generate_stream("KnowledgeArticle", stream_config, stream_api) + stream_2: IncrementalSalesforceStream = generate_stream("AcceptedEventRelation", stream_config, stream_api) stream_1.state_checkpoint_interval = 3 configure_request_params_mock(stream_1, stream_2) @@ -349,7 +323,7 @@ def test_rate_limit_rest(stream_config, stream_api, configured_catalog, state): m.register_uri("GET", stream_1.path(), json=response_1, status_code=200) m.register_uri("GET", next_page_url, json=response_2, status_code=403) - result = [i for i in source.read(logger=logger, config=stream_config, catalog=configured_catalog, state=state)] + result = [i for i in source.read(logger=logger, config=stream_config, catalog=rest_catalog, state=state)] assert stream_1.request_params.called assert ( @@ -360,14 +334,12 @@ def test_rate_limit_rest(stream_config, stream_api, configured_catalog, state): assert len(records) == 5 state_record = [item for item in result if item.type == Type.STATE][0] - assert state_record.state.data["Account"]["LastModifiedDate"] == "2021-11-17" + assert state_record.state.data["KnowledgeArticle"]["LastModifiedDate"] == "2021-11-17" def test_pagination_rest(stream_config, stream_api): - stream_name = "ActiveFeatureLicenseMetric" - state = {stream_name: {"SystemModstamp": "2122-08-22T05:08:29.000Z"}} - - stream: SalesforceStream = generate_stream(stream_name, stream_config, stream_api, state=state) + stream_name = "AcceptedEventRelation" + stream: SalesforceStream = generate_stream(stream_name, stream_config, stream_api) stream.DEFAULT_WAIT_TIMEOUT_SECONDS = 6 # maximum wait timeout will be 6 seconds next_page_url = "/services/data/v52.0/query/012345" with requests_mock.Mocker() as m: diff --git a/airbyte-integrations/connectors/source-salesforce/unit_tests/configured_catalog.json b/airbyte-integrations/connectors/source-salesforce/unit_tests/bulk_catalog.json similarity index 100% rename from airbyte-integrations/connectors/source-salesforce/unit_tests/configured_catalog.json rename to airbyte-integrations/connectors/source-salesforce/unit_tests/bulk_catalog.json diff --git a/airbyte-integrations/connectors/source-salesforce/unit_tests/conftest.py b/airbyte-integrations/connectors/source-salesforce/unit_tests/conftest.py index 7eafd85cb39b..e5996e13284c 100644 --- a/airbyte-integrations/connectors/source-salesforce/unit_tests/conftest.py +++ b/airbyte-integrations/connectors/source-salesforce/unit_tests/conftest.py @@ -11,9 +11,22 @@ from source_salesforce.source import SourceSalesforce +@pytest.fixture(autouse=True) +def time_sleep_mock(mocker): + time_mock = mocker.patch("time.sleep", lambda x: None) + yield time_mock + + +@pytest.fixture(scope="module") +def bulk_catalog(): + with open("unit_tests/bulk_catalog.json") as f: + data = json.loads(f.read()) + return ConfiguredAirbyteCatalog.parse_obj(data) + + @pytest.fixture(scope="module") -def configured_catalog(): - with open("unit_tests/configured_catalog.json") as f: +def rest_catalog(): + with open("unit_tests/rest_catalog.json") as f: data = json.loads(f.read()) return ConfiguredAirbyteCatalog.parse_obj(data) @@ -86,5 +99,5 @@ def stream_api_v2(stream_config): return _stream_api(stream_config, describe_response_data=describe_response_data) -def generate_stream(stream_name, stream_config, stream_api, state=None): - return SourceSalesforce.generate_streams(stream_config, {stream_name: None}, stream_api, state=state)[0] +def generate_stream(stream_name, stream_config, stream_api): + return SourceSalesforce.generate_streams(stream_config, {stream_name: None}, stream_api)[0] diff --git a/airbyte-integrations/connectors/source-salesforce/unit_tests/discovery_test.py b/airbyte-integrations/connectors/source-salesforce/unit_tests/discovery_test.py index c83df5f78184..cc1adccf2985 100644 --- a/airbyte-integrations/connectors/source-salesforce/unit_tests/discovery_test.py +++ b/airbyte-integrations/connectors/source-salesforce/unit_tests/discovery_test.py @@ -9,12 +9,6 @@ from source_salesforce.exceptions import TypeSalesforceException -@pytest.fixture(autouse=True) -def time_sleep_mock(mocker): - time_mock = mocker.patch("time.sleep", lambda x: None) - yield time_mock - - @pytest.mark.parametrize( "streams_criteria,predicted_filtered_streams", [ diff --git a/airbyte-integrations/connectors/source-salesforce/unit_tests/rest_catalog.json b/airbyte-integrations/connectors/source-salesforce/unit_tests/rest_catalog.json new file mode 100644 index 000000000000..25e05ac7f827 --- /dev/null +++ b/airbyte-integrations/connectors/source-salesforce/unit_tests/rest_catalog.json @@ -0,0 +1,28 @@ +{ + "streams": [ + { + "stream": { + "name": "KnowledgeArticle", + "json_schema": {}, + "supported_sync_modes": ["full_refresh", "incremental"], + "source_defined_cursor": true, + "default_cursor_field": ["LastModifiedDate"], + "source_defined_primary_key": [["Id"]] + }, + "sync_mode": "incremental", + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "AcceptedEventRelation", + "json_schema": {}, + "supported_sync_modes": ["full_refresh", "incremental"], + "source_defined_cursor": true, + "default_cursor_field": ["SystemModstamp"], + "source_defined_primary_key": [["Id"]] + }, + "sync_mode": "incremental", + "destination_sync_mode": "append" + } + ] +} diff --git a/airbyte-integrations/connectors/source-salesforce/unit_tests/test_memory.py b/airbyte-integrations/connectors/source-salesforce/unit_tests/test_memory.py index 0ae38cd50ba5..fcd05203dbbc 100644 --- a/airbyte-integrations/connectors/source-salesforce/unit_tests/test_memory.py +++ b/airbyte-integrations/connectors/source-salesforce/unit_tests/test_memory.py @@ -11,12 +11,6 @@ from source_salesforce.streams import BulkIncrementalSalesforceStream -@pytest.fixture(autouse=True) -def time_sleep_mock(mocker): - time_mock = mocker.patch("time.sleep", lambda x: None) - yield time_mock - - @pytest.mark.parametrize( "n_records, first_size, first_peak", ( diff --git a/docs/integrations/sources/salesforce.md b/docs/integrations/sources/salesforce.md index 88246d6392c1..13704b50af38 100644 --- a/docs/integrations/sources/salesforce.md +++ b/docs/integrations/sources/salesforce.md @@ -117,45 +117,46 @@ Now that you have set up the Salesforce source connector, check out the followin ## Changelog -| Version | Date | Pull Request | Subject | -|:--------|:-----------|:-------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------| -| 1.0.15 | 2022-08-30 | [16086](https://github.com/airbytehq/airbyte/pull/16086) | Improve API type detection | -| 1.0.14 | 2022-08-29 | [16119](https://github.com/airbytehq/airbyte/pull/16119) | Exclude `KnowledgeArticleVersion` from using bulk API | -| 1.0.13 | 2022-08-23 | [15901](https://github.com/airbytehq/airbyte/pull/15901) | Exclude `KnowledgeArticle` from using bulk API | -| 1.0.12 | 2022-08-09 | [15444](https://github.com/airbytehq/airbyte/pull/15444) | Fixed bug when `Bulk Job` was timeout by the connector, but remained running on the server | -| 1.0.11 | 2022-07-07 | [13729](https://github.com/airbytehq/airbyte/pull/13729) | Improve configuration field descriptions | -| 1.0.10 | 2022-06-09 | [13658](https://github.com/airbytehq/airbyte/pull/13658) | Correct logic to sync stream larger than page size | -| 1.0.9 | 2022-05-06 | [12685](https://github.com/airbytehq/airbyte/pull/12685) | Update CDK to v0.1.56 to emit an `AirbyeTraceMessage` on uncaught exceptions | -| 1.0.8 | 2022-05-04 | [12576](https://github.com/airbytehq/airbyte/pull/12576) | Decode responses as utf-8 and fallback to ISO-8859-1 if needed | -| 1.0.7 | 2022-05-03 | [12552](https://github.com/airbytehq/airbyte/pull/12552) | Decode responses as ISO-8859-1 instead of utf-8 | -| 1.0.6 | 2022-04-27 | [12335](https://github.com/airbytehq/airbyte/pull/12335) | Adding fixtures to mock time.sleep for connectors that explicitly sleep | -| 1.0.5 | 2022-04-25 | [12304](https://github.com/airbytehq/airbyte/pull/12304) | Add `Describe` stream | -| 1.0.4 | 2022-04-20 | [12230](https://github.com/airbytehq/airbyte/pull/12230) | Update connector to use a `spec.yaml` | -| 1.0.3 | 2022-04-04 | [11692](https://github.com/airbytehq/airbyte/pull/11692) | Optimised memory usage for `BULK` API calls | -| 1.0.2 | 2022-03-01 | [10751](https://github.com/airbytehq/airbyte/pull/10751) | Fix broken link anchor in connector configuration | -| 1.0.1 | 2022-02-27 | [10679](https://github.com/airbytehq/airbyte/pull/10679) | Reorganize input parameter order on the UI | -| 1.0.0 | 2022-02-27 | [10516](https://github.com/airbytehq/airbyte/pull/10516) | Speed up schema discovery by using parallelism | -| 0.1.23 | 2022-02-10 | [10141](https://github.com/airbytehq/airbyte/pull/10141) | Processing of failed jobs | -| 0.1.22 | 2022-02-02 | [10012](https://github.com/airbytehq/airbyte/pull/10012) | Increase CSV field_size_limit | -| 0.1.21 | 2022-01-28 | [9499](https://github.com/airbytehq/airbyte/pull/9499) | If a sync reaches daily rate limit it ends the sync early with success status. Read more in `Performance considerations` section | -| 0.1.20 | 2022-01-26 | [9757](https://github.com/airbytehq/airbyte/pull/9757) | Parse CSV with "unix" dialect | -| 0.1.19 | 2022-01-25 | [8617](https://github.com/airbytehq/airbyte/pull/8617) | Update connector fields title/description | -| 0.1.18 | 2022-01-20 | [9478](https://github.com/airbytehq/airbyte/pull/9478) | Add available stream filtering by `queryable` flag | -| 0.1.17 | 2022-01-19 | [9302](https://github.com/airbytehq/airbyte/pull/9302) | Deprecate API Type parameter | -| 0.1.16 | 2022-01-18 | [9151](https://github.com/airbytehq/airbyte/pull/9151) | Fix pagination in REST API streams | -| 0.1.15 | 2022-01-11 | [9409](https://github.com/airbytehq/airbyte/pull/9409) | Correcting the presence of an extra `else` handler in the error handling | -| 0.1.14 | 2022-01-11 | [9386](https://github.com/airbytehq/airbyte/pull/9386) | Handling 400 error, while `sobject` doesn't support `query` or `queryAll` requests | -| 0.1.13 | 2022-01-11 | [8797](https://github.com/airbytehq/airbyte/pull/8797) | Switched from authSpecification to advanced_auth in specefication | -| 0.1.12 | 2021-12-23 | [8871](https://github.com/airbytehq/airbyte/pull/8871) | Fix `examples` for new field in specification | -| 0.1.11 | 2021-12-23 | [8871](https://github.com/airbytehq/airbyte/pull/8871) | Add the ability to filter streams by user | -| 0.1.10 | 2021-12-23 | [9005](https://github.com/airbytehq/airbyte/pull/9005) | Handling 400 error when a stream is not queryable | -| 0.1.9 | 2021-12-07 | [8405](https://github.com/airbytehq/airbyte/pull/8405) | Filter 'null' byte(s) in HTTP responses | -| 0.1.8 | 2021-11-30 | [8191](https://github.com/airbytehq/airbyte/pull/8191) | Make `start_date` optional and change its format to `YYYY-MM-DD` | -| 0.1.7 | 2021-11-24 | [8206](https://github.com/airbytehq/airbyte/pull/8206) | Handling 400 error when trying to create a job for sync using Bulk API. | -| 0.1.6 | 2021-11-16 | [8009](https://github.com/airbytehq/airbyte/pull/8009) | Fix retring of BULK jobs | -| 0.1.5 | 2021-11-15 | [7885](https://github.com/airbytehq/airbyte/pull/7885) | Add `Transform` for output records | -| 0.1.4 | 2021-11-09 | [7778](https://github.com/airbytehq/airbyte/pull/7778) | Fix types for `anyType` fields | -| 0.1.3 | 2021-11-06 | [7592](https://github.com/airbytehq/airbyte/pull/7592) | Fix getting `anyType` fields using BULK API | -| 0.1.2 | 2021-09-30 | [6438](https://github.com/airbytehq/airbyte/pull/6438) | Annotate Oauth2 flow initialization parameters in connector specification | -| 0.1.1 | 2021-09-21 | [6209](https://github.com/airbytehq/airbyte/pull/6209) | Fix bug with pagination for BULK API | -| 0.1.0 | 2021-09-08 | [5619](https://github.com/airbytehq/airbyte/pull/5619) | Salesforce Aitbyte-Native Connector | +| Version | Date | Pull Request | Subject | +|:---------|:-----------|:---------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------| +| 1.0.16 | 2022-09-21 | [17001](https://github.com/airbytehq/airbyte/pull/17001) | Improve writing file of decode | +| 1.0.15 | 2022-08-30 | [16086](https://github.com/airbytehq/airbyte/pull/16086) | Improve API type detection | +| 1.0.14 | 2022-08-29 | [16119](https://github.com/airbytehq/airbyte/pull/16119) | Exclude `KnowledgeArticleVersion` from using bulk API | +| 1.0.13 | 2022-08-23 | [15901](https://github.com/airbytehq/airbyte/pull/15901) | Exclude `KnowledgeArticle` from using bulk API | +| 1.0.12 | 2022-08-09 | [15444](https://github.com/airbytehq/airbyte/pull/15444) | Fixed bug when `Bulk Job` was timeout by the connector, but remained running on the server | +| 1.0.11 | 2022-07-07 | [13729](https://github.com/airbytehq/airbyte/pull/13729) | Improve configuration field descriptions | +| 1.0.10 | 2022-06-09 | [13658](https://github.com/airbytehq/airbyte/pull/13658) | Correct logic to sync stream larger than page size | +| 1.0.9 | 2022-05-06 | [12685](https://github.com/airbytehq/airbyte/pull/12685) | Update CDK to v0.1.56 to emit an `AirbyeTraceMessage` on uncaught exceptions | +| 1.0.8 | 2022-05-04 | [12576](https://github.com/airbytehq/airbyte/pull/12576) | Decode responses as utf-8 and fallback to ISO-8859-1 if needed | +| 1.0.7 | 2022-05-03 | [12552](https://github.com/airbytehq/airbyte/pull/12552) | Decode responses as ISO-8859-1 instead of utf-8 | +| 1.0.6 | 2022-04-27 | [12335](https://github.com/airbytehq/airbyte/pull/12335) | Adding fixtures to mock time.sleep for connectors that explicitly sleep | +| 1.0.5 | 2022-04-25 | [12304](https://github.com/airbytehq/airbyte/pull/12304) | Add `Describe` stream | +| 1.0.4 | 2022-04-20 | [12230](https://github.com/airbytehq/airbyte/pull/12230) | Update connector to use a `spec.yaml` | +| 1.0.3 | 2022-04-04 | [11692](https://github.com/airbytehq/airbyte/pull/11692) | Optimised memory usage for `BULK` API calls | +| 1.0.2 | 2022-03-01 | [10751](https://github.com/airbytehq/airbyte/pull/10751) | Fix broken link anchor in connector configuration | +| 1.0.1 | 2022-02-27 | [10679](https://github.com/airbytehq/airbyte/pull/10679) | Reorganize input parameter order on the UI | +| 1.0.0 | 2022-02-27 | [10516](https://github.com/airbytehq/airbyte/pull/10516) | Speed up schema discovery by using parallelism | +| 0.1.23 | 2022-02-10 | [10141](https://github.com/airbytehq/airbyte/pull/10141) | Processing of failed jobs | +| 0.1.22 | 2022-02-02 | [10012](https://github.com/airbytehq/airbyte/pull/10012) | Increase CSV field_size_limit | +| 0.1.21 | 2022-01-28 | [9499](https://github.com/airbytehq/airbyte/pull/9499) | If a sync reaches daily rate limit it ends the sync early with success status. Read more in `Performance considerations` section | +| 0.1.20 | 2022-01-26 | [9757](https://github.com/airbytehq/airbyte/pull/9757) | Parse CSV with "unix" dialect | +| 0.1.19 | 2022-01-25 | [8617](https://github.com/airbytehq/airbyte/pull/8617) | Update connector fields title/description | +| 0.1.18 | 2022-01-20 | [9478](https://github.com/airbytehq/airbyte/pull/9478) | Add available stream filtering by `queryable` flag | +| 0.1.17 | 2022-01-19 | [9302](https://github.com/airbytehq/airbyte/pull/9302) | Deprecate API Type parameter | +| 0.1.16 | 2022-01-18 | [9151](https://github.com/airbytehq/airbyte/pull/9151) | Fix pagination in REST API streams | +| 0.1.15 | 2022-01-11 | [9409](https://github.com/airbytehq/airbyte/pull/9409) | Correcting the presence of an extra `else` handler in the error handling | +| 0.1.14 | 2022-01-11 | [9386](https://github.com/airbytehq/airbyte/pull/9386) | Handling 400 error, while `sobject` doesn't support `query` or `queryAll` requests | +| 0.1.13 | 2022-01-11 | [8797](https://github.com/airbytehq/airbyte/pull/8797) | Switched from authSpecification to advanced_auth in specefication | +| 0.1.12 | 2021-12-23 | [8871](https://github.com/airbytehq/airbyte/pull/8871) | Fix `examples` for new field in specification | +| 0.1.11 | 2021-12-23 | [8871](https://github.com/airbytehq/airbyte/pull/8871) | Add the ability to filter streams by user | +| 0.1.10 | 2021-12-23 | [9005](https://github.com/airbytehq/airbyte/pull/9005) | Handling 400 error when a stream is not queryable | +| 0.1.9 | 2021-12-07 | [8405](https://github.com/airbytehq/airbyte/pull/8405) | Filter 'null' byte(s) in HTTP responses | +| 0.1.8 | 2021-11-30 | [8191](https://github.com/airbytehq/airbyte/pull/8191) | Make `start_date` optional and change its format to `YYYY-MM-DD` | +| 0.1.7 | 2021-11-24 | [8206](https://github.com/airbytehq/airbyte/pull/8206) | Handling 400 error when trying to create a job for sync using Bulk API. | +| 0.1.6 | 2021-11-16 | [8009](https://github.com/airbytehq/airbyte/pull/8009) | Fix retring of BULK jobs | +| 0.1.5 | 2021-11-15 | [7885](https://github.com/airbytehq/airbyte/pull/7885) | Add `Transform` for output records | +| 0.1.4 | 2021-11-09 | [7778](https://github.com/airbytehq/airbyte/pull/7778) | Fix types for `anyType` fields | +| 0.1.3 | 2021-11-06 | [7592](https://github.com/airbytehq/airbyte/pull/7592) | Fix getting `anyType` fields using BULK API | +| 0.1.2 | 2021-09-30 | [6438](https://github.com/airbytehq/airbyte/pull/6438) | Annotate Oauth2 flow initialization parameters in connector specification | +| 0.1.1 | 2021-09-21 | [6209](https://github.com/airbytehq/airbyte/pull/6209) | Fix bug with pagination for BULK API | +| 0.1.0 | 2021-09-08 | [5619](https://github.com/airbytehq/airbyte/pull/5619) | Salesforce Aitbyte-Native Connector |