diff --git a/airbyte-integrations/connectors/source-drift/build.gradle b/airbyte-integrations/connectors/source-drift/build.gradle index f88c8f7fa9b0..9aec66442ce6 100644 --- a/airbyte-integrations/connectors/source-drift/build.gradle +++ b/airbyte-integrations/connectors/source-drift/build.gradle @@ -1,13 +1,11 @@ plugins { id 'airbyte-python' id 'airbyte-docker' - id 'airbyte-source-acceptance-test' + // TODO + // id 'airbyte-source-acceptance-test' } airbytePython { moduleDirectory 'source_drift' } -dependencies { - implementation files(project(':airbyte-integrations:bases:source-acceptance-test').airbyteDocker.outputs) -} diff --git a/airbyte-integrations/connectors/source-sf-marketingcloud-singer/.dockerignore b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/.dockerignore new file mode 100644 index 000000000000..ebfbd4a331b7 --- /dev/null +++ b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/.dockerignore @@ -0,0 +1,7 @@ +* +!Dockerfile +!Dockerfile.test +!main.py +!source_sf_marketingcloud_singer +!setup.py +!secrets diff --git a/airbyte-integrations/connectors/source-sf-marketingcloud-singer/.gitignore b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/.gitignore new file mode 100644 index 000000000000..29fffc6a50cc --- /dev/null +++ b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/.gitignore @@ -0,0 +1 @@ +NEW_SOURCE_CHECKLIST.md diff --git a/airbyte-integrations/connectors/source-sf-marketingcloud-singer/Dockerfile b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/Dockerfile new file mode 100644 index 000000000000..bb35c17e0c11 --- /dev/null +++ b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.7-slim + +# Bash is installed for more convenient debugging. +RUN apt-get update -y && apt-get install -y bash && apt-get install -y gcc && apt-get install -y git && rm -rf /var/lib/apt/lists/* + +WORKDIR /airbyte/integration_code +COPY source_sf_marketingcloud_singer ./source_sf_marketingcloud_singer +COPY main.py ./ +COPY setup.py ./ +RUN pip install . + +ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" +ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] + +LABEL io.airbyte.version=0.1.0 +LABEL io.airbyte.name=airbyte/source-sf-marketingcloud-singer diff --git a/airbyte-integrations/connectors/source-sf-marketingcloud-singer/README.md b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/README.md new file mode 100644 index 000000000000..c48e9989219c --- /dev/null +++ b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/README.md @@ -0,0 +1,129 @@ +# Source Sf Marketingcloud Singer + +This is the repository for the Sf Marketingcloud source connector, based on a Singer tap. +For information about how to use this connector within Airbyte, see [the User Documentation](https://docs.airbyte.io/integrations/sources/sf-marketingcloud). + +## Local development + +### Prerequisites +**To iterate on this connector, make sure to complete this prerequisites section.** + +#### Minimum Python version required `= 3.7.0` + +#### Build & Activate Virtual Environment and install dependencies +From this connector directory, create a virtual environment: +``` +python -m venv .venv +``` + +This will generate a virtualenv for this module in `.venv/`. Make sure this venv is active in your +development environment of choice. To activate it from the terminal, run: +``` +source .venv/bin/activate +pip install -r requirements.txt +``` +If you are in an IDE, follow your IDE's instructions to activate the virtualenv. + +Note that while we are installing dependencies from `requirements.txt`, you should only edit `setup.py` for your dependencies. `requirements.txt` is +used for editable installs (`pip install -e`) to pull in Python dependencies from the monorepo and will call `setup.py`. +If this is mumbo jumbo to you, don't worry about it, just put your deps in `setup.py` but install using `pip install -r requirements.txt` and everything +should work as you expect. + +#### Building via Gradle +From the Airbyte repository root, run: +``` +./gradlew :airbyte-integrations:connectors:source-sf-marketingcloud:build +``` + +#### Create credentials +**If you are a community contributor**, follow the instructions in the [documentation](https://docs.airbyte.io/integrations/sources/sf-marketingcloud) +to generate the necessary credentials. Then create a file `secrets/config.json` conforming to the `source_sf_marketingcloud_singer/spec.json` file. +Note that the `secrets` directory is gitignored by default, so there is no danger of accidentally checking in sensitive information. +See `integration_tests/sample_config.json` for a sample config file. + +**If you are an Airbyte core member**, copy the credentials in Lastpass under the secret name `source sf-marketingcloud test creds` +and place them into `secrets/config.json`. + +### Locally running the connector +``` +python main.py spec +python main.py check --config secrets/config.json +python main.py discover --config secrets/config.json +python main.py read --config secrets/config.json --catalog integration_tests/configured_catalog.json +``` + +### Locally running the connector docker image + +#### Build +First, make sure you build the latest Docker image: +``` +docker build . -t airbyte/source-sf-marketingcloud-singer:dev +``` + +You can also build the connector image via Gradle: +``` +./gradlew :airbyte-integrations:connectors:source-sf-marketingcloud:airbyteDocker +``` +When building via Gradle, the docker image name and tag, respectively, are the values of the `io.airbyte.name` and `io.airbyte.version` `LABEL`s in +the Dockerfile. + +#### Run +Then run any of the connector commands as follows: +``` +docker run --rm airbyte/source-sf-marketingcloud-singer:dev spec +docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-sf-marketingcloud-singer:dev check --config /secrets/config.json +docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-sf-marketingcloud-singer:dev discover --config /secrets/config.json +docker run --rm -v $(pwd)/secrets:/secrets -v $(pwd)/integration_tests:/integration_tests airbyte/source-sf-marketingcloud-singer:dev read --config /secrets/config.json --catalog /integration_tests/configured_catalog.json +``` +## Testing + Make sure to familiarize yourself with [pytest test discovery](https://docs.pytest.org/en/latest/goodpractices.html#test-discovery) to know how your test files and methods should be named. +First install test dependencies into your virtual environment: +``` +pip install .[tests] +``` +### Unit Tests +To run unit tests locally, from the connector directory run: +``` +python -m pytest unit_tests +``` + +### Integration Tests +There are two types of integration tests: Acceptance Tests (Airbyte's test suite for all source connectors) and custom integration tests (which are specific to this connector). +#### Custom Integration tests +Place custom tests inside `integration_tests/` folder, then, from the connector root, run +``` +python -m pytest integration_tests +``` +#### Acceptance Tests +Customize `acceptance-test-config.yml` file to configure tests. See [Source Acceptance Tests](source-acceptance-tests.md) for more information. +If your connector requires to create or destroy resources for use during acceptance tests create fixtures for it and place them inside integration_tests/acceptance.py. +To run your integration tests with acceptance tests, from the connector root, run +``` +python -m pytest integration_tests -p integration_tests.acceptance +``` +To run your integration tests with docker + +### Using gradle to run tests +All commands should be run from airbyte project root. +To run unit tests: +``` +./gradlew :airbyte-integrations:connectors:source-sf-marketingcloud-singer:unitTest +``` +To run acceptance and custom integration tests: +``` +./gradlew :airbyte-integrations:connectors:source-sf-marketingcloud-singer:integrationTest +``` + +## Dependency Management +All of your dependencies should go in `setup.py`, NOT `requirements.txt`. The requirements file is only used to connect internal Airbyte dependencies in the monorepo for local development. +We split dependencies between two groups, dependencies that are: +* required for your connector to work need to go to `MAIN_REQUIREMENTS` list. +* required for the testing need to go to `TEST_REQUIREMENTS` list + +### Publishing a new version of the connector +You've checked out the repo, implemented a million dollar feature, and you're ready to share your changes with the world. Now what? +1. Make sure your changes are passing unit and integration tests. +1. Bump the connector version in `Dockerfile` -- just increment the value of the `LABEL io.airbyte.version` appropriately (we use [SemVer](https://semver.org/)). +1. Create a Pull Request. +1. Pat yourself on the back for being an awesome contributor. +1. Someone from Airbyte will take a look at your PR and iterate with you to merge it into master. diff --git a/airbyte-integrations/connectors/source-sf-marketingcloud-singer/acceptance-test-config.yml b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/acceptance-test-config.yml new file mode 100644 index 000000000000..2eba0ea7f354 --- /dev/null +++ b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/acceptance-test-config.yml @@ -0,0 +1,19 @@ +# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# for more information about how to configure these tests +connector_image: airbyte/source-sf-marketingcloud-singer:dev +tests: + spec: + - spec_path: "source_sf_marketingcloud_singer/spec.json" + connection: + - config_path: "secrets/config.json" + status: "succeed" + discovery: + - config_path: "secrets/config.json" + # Uncomment this when you upgrade airbyte-cdk to the latest version and + # Once you have data atleast in one stream + # basic_read: + # - config_path: "secrets/config.json" + # configured_catalog_path: "integration_tests/configured_catalog.json" + # expect_trace_message_on_failure: false + # empty_streams: + # - email diff --git a/airbyte-integrations/connectors/source-sf-marketingcloud-singer/acceptance-test-docker.sh b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/acceptance-test-docker.sh new file mode 100755 index 000000000000..e4d8b1cef896 --- /dev/null +++ b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/acceptance-test-docker.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env sh + +# Build latest connector image +docker build . -t $(cat acceptance-test-config.yml | grep "connector_image" | head -n 1 | cut -d: -f2) + +# Pull latest acctest image +docker pull airbyte/source-acceptance-test:latest + +# Run +docker run --rm -it \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v /tmp:/tmp \ + -v $(pwd):/test_input \ + airbyte/source-acceptance-test \ + --acceptance-test-config /test_input + diff --git a/airbyte-integrations/connectors/source-sf-marketingcloud-singer/build.gradle b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/build.gradle new file mode 100644 index 000000000000..3d1c93ff39f5 --- /dev/null +++ b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/build.gradle @@ -0,0 +1,13 @@ +plugins { + id 'airbyte-python' + id 'airbyte-docker' + id 'airbyte-source-acceptance-test' +} + +airbytePython { + moduleDirectory 'source_sf_marketingcloud_singer' +} + +dependencies { + implementation files(project(':airbyte-integrations:bases:source-acceptance-test').airbyteDocker.outputs) +} diff --git a/airbyte-integrations/connectors/source-sf-marketingcloud-singer/integration_tests/__init__.py b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/integration_tests/__init__.py new file mode 100644 index 000000000000..9db886e0930f --- /dev/null +++ b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/integration_tests/__init__.py @@ -0,0 +1,23 @@ +# +# MIT License +# +# Copyright (c) 2020 Airbyte +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# diff --git a/airbyte-integrations/connectors/source-sf-marketingcloud-singer/integration_tests/acceptance.py b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/integration_tests/acceptance.py new file mode 100644 index 000000000000..950b53b59d41 --- /dev/null +++ b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/integration_tests/acceptance.py @@ -0,0 +1,14 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import pytest + +pytest_plugins = ("source_acceptance_test.plugin",) + + +@pytest.fixture(scope="session", autouse=True) +def connector_setup(): + """This fixture is a placeholder for external resources that acceptance test might require.""" + yield diff --git a/airbyte-integrations/connectors/source-sf-marketingcloud-singer/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/integration_tests/configured_catalog.json new file mode 100644 index 000000000000..73865c2d9660 --- /dev/null +++ b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/integration_tests/configured_catalog.json @@ -0,0 +1,16 @@ +{ + "streams": [ + { + "stream": { + "name": "email", + "json_schema": {}, + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["ModifiedDate"] + }, + "sync_mode": "full_refresh", + "cursor_field": ["ModifiedDate"], + "destination_sync_mode": "append" + } + ] +} diff --git a/airbyte-integrations/connectors/source-sf-marketingcloud-singer/integration_tests/invalid_config.json b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/integration_tests/invalid_config.json new file mode 100644 index 000000000000..034e08b24fe1 --- /dev/null +++ b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/integration_tests/invalid_config.json @@ -0,0 +1,16 @@ + +{ + "client_id": "fake-client-id", + "client_secret": "fake-client-secret", + "tenant_subdomain": "fake-domain", + "start_date": "2021-10-06T00:00:00Z", + "request_timeout": 900, + "batch_size": 2500, + + "pagination__sent_interval_quantity": 10, + "pagination__click_interval_quantity": 30, + "pagination__open_interval_quantity": 30, + "pagination__bounce_interval_quantity": 30, + "pagination__unsub_interval_quantity": 30, + "pagination__list_subscriber_interval_quantity": 7 +} diff --git a/airbyte-integrations/connectors/source-sf-marketingcloud-singer/integration_tests/sample_config.json b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/integration_tests/sample_config.json new file mode 100644 index 000000000000..7135ad730dbf --- /dev/null +++ b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/integration_tests/sample_config.json @@ -0,0 +1,15 @@ +{ + "client_id": "", + "client_secret": "", + "tenant_subdomain": "", + "start_date": "2021-10-06T00:00:00Z", + "request_timeout": 900, + "batch_size": 2500, + + "pagination__sent_interval_quantity": 10, + "pagination__click_interval_quantity": 30, + "pagination__open_interval_quantity": 30, + "pagination__bounce_interval_quantity": 30, + "pagination__unsub_interval_quantity": 30, + "pagination__list_subscriber_interval_quantity": 7 +} diff --git a/airbyte-integrations/connectors/source-sf-marketingcloud-singer/main.py b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/main.py new file mode 100644 index 000000000000..e747ed900c89 --- /dev/null +++ b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/main.py @@ -0,0 +1,12 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +import sys + +from airbyte_cdk.entrypoint import launch +from source_sf_marketingcloud_singer import SourceSfMarketingcloudSinger + +if __name__ == "__main__": + source = SourceSfMarketingcloudSinger() + launch(source, sys.argv[1:]) diff --git a/airbyte-integrations/connectors/source-sf-marketingcloud-singer/requirements.txt b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/requirements.txt new file mode 100644 index 000000000000..7be17a56d745 --- /dev/null +++ b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/requirements.txt @@ -0,0 +1,3 @@ +# This file is autogenerated -- only edit if you know what you are doing. Use setup.py for declaring dependencies. +-e ../../bases/source-acceptance-test +-e . diff --git a/airbyte-integrations/connectors/source-sf-marketingcloud-singer/setup.py b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/setup.py new file mode 100644 index 000000000000..7878b7542dc9 --- /dev/null +++ b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/setup.py @@ -0,0 +1,28 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +from setuptools import find_packages, setup + +MAIN_REQUIREMENTS = [ + "airbyte-cdk~=0.1", + "Salesforce-FuelSDK-Sans", + "tap-exacttarget-remove-sud==1.7.4", +] + +TEST_REQUIREMENTS = [ + "pytest~=6.1", +] + +setup( + name="source_sf_marketingcloud_singer", + description="Source implementation for Sf Marketingcloud, built on the Singer tap implementation.", + author="Airbyte", + author_email="contact@airbyte.io", + packages=find_packages(), + install_requires=MAIN_REQUIREMENTS, + package_data={"": ["*.json"]}, + extras_require={ + "tests": TEST_REQUIREMENTS, + }, +) diff --git a/airbyte-integrations/connectors/source-sf-marketingcloud-singer/source_sf_marketingcloud_singer/__init__.py b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/source_sf_marketingcloud_singer/__init__.py new file mode 100644 index 000000000000..3c07f93022c4 --- /dev/null +++ b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/source_sf_marketingcloud_singer/__init__.py @@ -0,0 +1,26 @@ +# MIT License +# +# Copyright (c) 2020 Airbyte +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +from .source import SourceSfMarketingcloudSinger + +__all__ = ["SourceSfMarketingcloudSinger"] diff --git a/airbyte-integrations/connectors/source-sf-marketingcloud-singer/source_sf_marketingcloud_singer/source.py b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/source_sf_marketingcloud_singer/source.py new file mode 100644 index 000000000000..039fc0eb5056 --- /dev/null +++ b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/source_sf_marketingcloud_singer/source.py @@ -0,0 +1,83 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import json + +import FuelSDK +from airbyte_cdk.logger import AirbyteLogger +from airbyte_cdk.models import AirbyteConnectionStatus, Status +from airbyte_cdk.sources.singer import SingerSource +from suds.transport.https import HttpAuthenticated + + +class SourceSfMarketingcloudSinger(SingerSource): + TAP_CMD = "tap-exacttarget" + + def check_config(self, logger: AirbyteLogger, config_path: str, config: json) -> AirbyteConnectionStatus: + """ + Tests if the input configuration can be used to successfully connect to the integration + e.g: if a provided Stripe API token can be used to connect to the Stripe API. + + :param logger: Logging object to display debug/info/error to the logs + (logs will not be accessible via airbyte UI if they are not passed to this logger) + :param config_path: Path to the file containing the configuration json config + :param config: Json object containing the configuration of this source, content of this json is as specified in + the properties of the spec.json file + + :return: AirbyteConnectionStatus indicating a Success or Failure + """ + local_config = { + "clientid": config["client_id"], + "clientsecret": config["client_secret"], + "tenant_subdomain": config["tenant_subdomain"], + "start_date": config["start_date"], + "request_timeout": "50000", + "useOAuth2Authentication": "True", + "authenticationurl": "", + } + # taken from line 52 - 80 in tap_exacttarget/client.py + try: + logger.info("Trying to authenticate using V1 endpoint") + local_config["useOAuth2Authentication"] = "False" + auth_stub = FuelSDK.ET_Client(params=local_config) + transport = HttpAuthenticated(timeout=int(local_config.get("request_timeout", 900))) + auth_stub.soap_client.set_options(transport=transport) + logger.info("Success.") + except Exception as e: + logger.info("Failed to auth using V1 endpoint") + if not local_config.get("tenant_subdomain"): + logger.info("No tenant_subdomain found, will not attempt to auth with V2 endpoint") + raise e + + # Next try V2 + # Move to OAuth2: https://help.salesforce.com/articleView?id=mc_rn_january_2019_platform_ip_remove_legacy_package_create_ability.htm&type=5 + try: + logger.info("Trying to authenticate using V2 endpoint") + local_config["useOAuth2Authentication"] = "True" + local_config["authenticationurl"] = "https://{}.auth.marketingcloudapis.com".format(local_config["tenant_subdomain"]) + auth_stub = FuelSDK.ET_Client(params=local_config) + transport = HttpAuthenticated(timeout=int(local_config.get("request_timeout", 900))) + auth_stub.soap_client.set_options(transport=transport) + return AirbyteConnectionStatus(status=Status.SUCCEEDED) + except Exception as e: + logger.info("Failed to auth using V2 endpoint") + raise e + logger.info("Login succeeded") + return AirbyteConnectionStatus(status=Status.FAILED, message=f"An exception occurred: {str(e)}") + + def discover_cmd(self, logger: AirbyteLogger, config_path: str) -> str: + """ + Return the string commands to invoke the tap with the --discover flag and the right configuration options + """ + return f"tap-exacttarget --config {config_path} --discover" + + def read_cmd(self, logger: AirbyteLogger, config_path: str, catalog_path: str, state_path: str = None) -> str: + """ + Return the string commands to invoke the tap with the right configuration options to read data from the source + """ + config_option = f"--config {config_path}" + properties_option = f"--properties {catalog_path}" + state_option = f"--state {state_path}" if state_path else "" + return f"{self.TAP_CMD} {config_option} {properties_option} {state_option}" diff --git a/airbyte-integrations/connectors/source-sf-marketingcloud-singer/source_sf_marketingcloud_singer/spec.json b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/source_sf_marketingcloud_singer/spec.json new file mode 100644 index 000000000000..c23e3c06e0d6 --- /dev/null +++ b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/source_sf_marketingcloud_singer/spec.json @@ -0,0 +1,78 @@ +{ + "documentationUrl": "https://docs.airbyte.io/integrations/sources/sf_marketingcloud", + "connectionSpecification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Source Salesforce Marketing Cloud Singer Spec", + "type": "object", + "required": [ + "client_id", + "client_secret", + "tenant_subdomain", + "start_date", + "batch_size", + "request_timeout" + ], + "additionalProperties": false, + "properties": { + "client_id": { + "type": "string", + "description": "as per MC app", + "airbyte_secret": true + }, + "client_secret": { + "type": "string", + "description": "as per MC app", + "airbyte_secret": true + }, + "tenant_subdomain": { + "type": "string", + "description": "as per MC app" + }, + "start_date": { + "type": "string", + "description": "date from which data is retrieved", + "example": "2019-01-01T00:00:00Z" + }, + "batch_size": { + "type": "integer", + "description": "sync batches", + "example": "2500" + }, + "request_timeout": { + "type": "integer", + "description": "request timeout", + "example": "900" + }, + "pagination__sent_interval_quantity": { + "type": "integer", + "description": "Length of pagination interval in minutes, for Sent events", + "example": "10" + }, + "pagination__click_interval_quantity": { + "type": "integer", + "description": "Length of pagination interval in minutes, for Click events", + "example": "30" + }, + "pagination__open_interval_quantity": { + "type": "integer", + "description": "Length of pagination interval in minutes, for Open events", + "example": "30" + }, + "pagination__bounce_interval_quantity": { + "type": "integer", + "description": "Length of pagination interval in minutes, for Bounce events", + "example": "30" + }, + "pagination__unsub_interval_quantity": { + "type": "integer", + "description": "Length of pagination interval in minutes, for Unsub events", + "example": "30" + }, + "pagination__list_subscriber_interval_quantity": { + "type": "integer", + "description": "Length of pagination interval in days, for List Subscriber", + "example": "7" + } + } + } +} diff --git a/airbyte-integrations/connectors/source-sf-marketingcloud-singer/unit_tests/unit_test.py b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/unit_tests/unit_test.py new file mode 100644 index 000000000000..dddaea0060fa --- /dev/null +++ b/airbyte-integrations/connectors/source-sf-marketingcloud-singer/unit_tests/unit_test.py @@ -0,0 +1,7 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +def test_example_method(): + assert True