diff --git a/airbyte-config/init/src/main/resources/config/STANDARD_SOURCE_DEFINITION/b03a9f3e-22a5-11eb-adc1-0242ac120002.json b/airbyte-config/init/src/main/resources/config/STANDARD_SOURCE_DEFINITION/b03a9f3e-22a5-11eb-adc1-0242ac120002.json index 0054aeb5ed724..c6151e365560d 100644 --- a/airbyte-config/init/src/main/resources/config/STANDARD_SOURCE_DEFINITION/b03a9f3e-22a5-11eb-adc1-0242ac120002.json +++ b/airbyte-config/init/src/main/resources/config/STANDARD_SOURCE_DEFINITION/b03a9f3e-22a5-11eb-adc1-0242ac120002.json @@ -2,7 +2,7 @@ "sourceDefinitionId": "b03a9f3e-22a5-11eb-adc1-0242ac120002", "name": "Mailchimp", "dockerRepository": "airbyte/source-mailchimp", - "dockerImageTag": "0.2.1", + "dockerImageTag": "0.2.2", "documentationUrl": "https://hub.docker.com/r/airbyte/source-mailchimp", "icon": "mailchimp.svg" } 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 4883913082844..ef6e9f47d2257 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -84,7 +84,7 @@ - sourceDefinitionId: b03a9f3e-22a5-11eb-adc1-0242ac120002 name: Mailchimp dockerRepository: airbyte/source-mailchimp - dockerImageTag: 0.2.1 + dockerImageTag: 0.2.2 documentationUrl: https://hub.docker.com/r/airbyte/source-mailchimp icon: mailchimp.svg - sourceDefinitionId: 39f092a6-8c87-4f6f-a8d9-5cef45b7dbe1 diff --git a/airbyte-integrations/connectors/source-mailchimp/CHANGELOG.md b/airbyte-integrations/connectors/source-mailchimp/CHANGELOG.md new file mode 100644 index 0000000000000..d89a6308e2669 --- /dev/null +++ b/airbyte-integrations/connectors/source-mailchimp/CHANGELOG.md @@ -0,0 +1,4 @@ +# Changelog + +## 0.2.1 +CDK version (with using the new Source Acceptance tests) diff --git a/airbyte-integrations/connectors/source-mailchimp/Dockerfile b/airbyte-integrations/connectors/source-mailchimp/Dockerfile index ff4de4ba8b861..e3457f2b64b8a 100644 --- a/airbyte-integrations/connectors/source-mailchimp/Dockerfile +++ b/airbyte-integrations/connectors/source-mailchimp/Dockerfile @@ -1,16 +1,15 @@ -FROM airbyte/integration-base-python:0.1.1 +FROM python:3.7-slim # Bash is installed for more convenient debugging. RUN apt-get update && apt-get install -y bash && rm -rf /var/lib/apt/lists/* -ENV CODE_PATH="source_mailchimp" -ENV AIRBYTE_IMPL_MODULE="source_mailchimp" -ENV AIRBYTE_IMPL_PATH="SourceMailchimp" - WORKDIR /airbyte/integration_code -COPY $CODE_PATH ./$CODE_PATH +COPY source_mailchimp ./source_mailchimp +COPY main.py ./ COPY setup.py ./ RUN pip install . -LABEL io.airbyte.version=0.2.1 +ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] + +LABEL io.airbyte.version=0.2.2 LABEL io.airbyte.name=airbyte/source-mailchimp diff --git a/airbyte-integrations/connectors/source-mailchimp/README.md b/airbyte-integrations/connectors/source-mailchimp/README.md index b928c3608b632..9abbaae2e4bd1 100644 --- a/airbyte-integrations/connectors/source-mailchimp/README.md +++ b/airbyte-integrations/connectors/source-mailchimp/README.md @@ -1,6 +1,6 @@ -# Mailchimp Source +# Mailchimp Source -This is the repository for the Mailchimp source connector, written in Python. +This is the repository for the Mailchimp source connector, written in Python. For information about how to use this connector within Airbyte, see [the documentation](https://docs.airbyte.io/integrations/sources/mailchimp). ## Local development @@ -28,7 +28,9 @@ If this is mumbo jumbo to you, don't worry about it, just put your deps in `setu should work as you expect. #### Building via Gradle -From the Airbyte repository root, run: +You can also build the connector in Gradle. This is typically used in CI and not needed for your development workflow. + +To build using Gradle, from the Airbyte repository root, run: ``` ./gradlew :airbyte-integrations:connectors:source-mailchimp:build ``` @@ -36,25 +38,18 @@ From the Airbyte repository root, run: #### Create credentials **If you are a community contributor**, follow the instructions in the [documentation](https://docs.airbyte.io/integrations/sources/mailchimp) to generate the necessary credentials. Then create a file `secrets/config.json` conforming to the `source_mailchimp/spec.json` file. -Note that the `secrets` directory is gitignored by default, so there is no danger of accidentally checking in sensitive information. +Note that any directory named `secrets` is gitignored across the entire Airbyte repo, so there is no danger of accidentally checking in sensitive information. See `sample_files/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 mailchimp test creds` and place them into `secrets/config.json`. - ### Locally running the connector ``` -python main_dev.py spec -python main_dev.py check --config secrets/config.json -python main_dev.py discover --config secrets/config.json -python main_dev.py read --config secrets/config.json --catalog sample_files/configured_catalog.json -``` - -### Unit Tests -To run unit tests locally, from the connector directory run: -``` -python -m pytest unit_tests +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 sample_files/configured_catalog.json ``` ### Locally running the connector docker image @@ -80,19 +75,55 @@ docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-mailchimp:dev check -- docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-mailchimp:dev discover --config /secrets/config.json docker run --rm -v $(pwd)/secrets:/secrets -v $(pwd)/sample_files:/sample_files airbyte/source-mailchimp:dev read --config /secrets/config.json --catalog /sample_files/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 -1. From the airbyte project root, run `./gradlew :airbyte-integrations:connectors:source-mailchimp:integrationTest` to run the standard integration test suite. -1. To run additional integration tests, place your integration tests in a new directory `integration_tests` and run them with `python -m pytest -s integration_tests`. - 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. +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 unittest run: +``` +./gradlew :airbyte-integrations:connectors:source-mailchimp:unitTest +``` +To run acceptance and custom integration tests run: +``` +./gradlew :airbyte-integrations:connectors:source-mailchimp: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). +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-mailchimp/acceptance-test-config.yml b/airbyte-integrations/connectors/source-mailchimp/acceptance-test-config.yml new file mode 100644 index 0000000000000..bcbd2cbd8bc3c --- /dev/null +++ b/airbyte-integrations/connectors/source-mailchimp/acceptance-test-config.yml @@ -0,0 +1,33 @@ +connector_image: airbyte/source-mailchimp:dev +tests: + spec: + - spec_path: "source_mailchimp/spec.json" + connection: + - config_path: "secrets/config.json" + status: "succeed" + - config_path: "integration_tests/invalid_config.json" + status: "failed" + discovery: + - config_path: "secrets/config.json" + basic_read: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" + validate_output_from_all_streams: yes +# THIS TEST IS COMMENTED OUT. Tests are supposed to accept +# `state = {cursor_field: value}`. When we have dependent endpoint path +# `path_begin/{some_id}/path_end` we need a complex state like below: +# `{"id1": {cursor_field: value}, "id2": {cursor_field: value}...}` +# The test currently is not supposed to accept this desired construction, +# so it is commented out + +# incremental: +# - config_path: "secrets/config.json" +# configured_catalog_path: "integration_tests/configured_catalog.json" +# state_path: "integration_tests/state.json" +# cursor_paths: +# lists: [ "date_created" ] +# campaigns: [ "create_time" ] +# Email_activity: [ "timestamp" ] + full_refresh: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" diff --git a/airbyte-integrations/connectors/source-mailchimp/acceptance-test-docker.sh b/airbyte-integrations/connectors/source-mailchimp/acceptance-test-docker.sh new file mode 100644 index 0000000000000..1425ff74f1511 --- /dev/null +++ b/airbyte-integrations/connectors/source-mailchimp/acceptance-test-docker.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env sh +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-mailchimp/build.gradle b/airbyte-integrations/connectors/source-mailchimp/build.gradle index 46edd086a2bef..dc48f4f56987e 100644 --- a/airbyte-integrations/connectors/source-mailchimp/build.gradle +++ b/airbyte-integrations/connectors/source-mailchimp/build.gradle @@ -1,23 +1,13 @@ plugins { id 'airbyte-python' id 'airbyte-docker' - id 'airbyte-standard-source-test-file' + id 'airbyte-source-acceptance-test' } airbytePython { moduleDirectory 'source_mailchimp' } - -airbyteStandardSourceTestFile { - specPath = "source_mailchimp/spec.json" - configPath = "secrets/config.json" - configuredCatalogPath = "sample_files/configured_catalog.json" -} - - - dependencies { - implementation files(project(':airbyte-integrations:bases:base-standard-source-test-file').airbyteDocker.outputs) - implementation files(project(':airbyte-integrations:bases:base-python').airbyteDocker.outputs) + implementation files(project(':airbyte-integrations:bases:source-acceptance-test').airbyteDocker.outputs) } diff --git a/airbyte-integrations/connectors/source-mailchimp/integration_tests/__init__.py b/airbyte-integrations/connectors/source-mailchimp/integration_tests/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/airbyte-integrations/connectors/source-mailchimp/integration_tests/integration_source_test.py b/airbyte-integrations/connectors/source-mailchimp/integration_tests/acceptance.py similarity index 55% rename from airbyte-integrations/connectors/source-mailchimp/integration_tests/integration_source_test.py rename to airbyte-integrations/connectors/source-mailchimp/integration_tests/acceptance.py index 48c8b0d245086..d98ac8aa3a1c7 100644 --- a/airbyte-integrations/connectors/source-mailchimp/integration_tests/integration_source_test.py +++ b/airbyte-integrations/connectors/source-mailchimp/integration_tests/acceptance.py @@ -23,34 +23,14 @@ # -import json - import pytest -from source_mailchimp.client import Client - - -class TestSourceMailchimp: - mailchimp_config: str = "../secrets/config.json" - - @pytest.fixture() - def get_client(self): - with open(self.mailchimp_config) as json_file: - config = json.load(json_file) - client = Client(username=config["username"], apikey=config["apikey"]) - return client - - def test_client_right_credentials(self, get_client): - status, error = get_client.health_check() - assert status - def test_client_getting_streams(self, get_client): - streams = get_client.get_streams() - assert streams +pytest_plugins = ("source_acceptance_test.plugin",) - def test_client_read_lists(self, get_client): - lists = list(get_client.lists()) - assert isinstance(lists, list) - def test_client_read_campaigns(self, get_client): - campaigns = list(get_client.campaigns()) - assert isinstance(campaigns, list) +@pytest.fixture(scope="session", autouse=True) +def connector_setup(): + """This fixture is a placeholder for external resources that acceptance test might require.""" + # TODO: setup test dependencies + yield + # TODO: clean up test dependencies diff --git a/airbyte-integrations/connectors/source-mailchimp/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-mailchimp/integration_tests/configured_catalog.json new file mode 100644 index 0000000000000..8685a0a679b68 --- /dev/null +++ b/airbyte-integrations/connectors/source-mailchimp/integration_tests/configured_catalog.json @@ -0,0 +1,1151 @@ +{ + "streams": [ + { + "stream": { + "name": "campaigns", + "json_schema": { + "type": "object", + "title": "Campaign", + "description": "A summary of an individual campaign's settings and content.", + "properties": { + "id": { + "type": "string", + "title": "Campaign ID", + "description": "A string that uniquely identifies this campaign.", + "readOnly": true + }, + "web_id": { + "type": "integer", + "title": "Campaign Web ID", + "description": "The ID used in the Mailchimp web application. View this campaign in your Mailchimp account at `https://{dc}.admin.mailchimp.com/campaigns/show/?id={web_id}`.", + "readOnly": true + }, + "parent_campaign_id": { + "type": "string", + "title": "Parent Campaign ID", + "description": "If this campaign is the child of another campaign, this identifies the parent campaign. For Example, for RSS or Automation children.", + "readOnly": true + }, + "type": { + "$ref": "https://us1.api.mailchimp.com/schema/3.0/Definitions/CampaignType.json" + }, + "create_time": { + "type": "string", + "format": "date-time", + "title": "Create Time", + "description": "The date and time the campaign was created in ISO 8601 format.", + "readOnly": true + }, + "archive_url": { + "type": "string", + "title": "Archive URL", + "description": "The link to the campaign's archive version in ISO 8601 format.", + "readOnly": true + }, + "long_archive_url": { + "type": "string", + "title": "Long Archive URL", + "description": "The original link to the campaign's archive version.", + "readOnly": true + }, + "status": { + "$ref": "https://us1.api.mailchimp.com/schema/3.0/Definitions/CampaignStatus.json" + }, + "emails_sent": { + "type": "integer", + "title": "Emails Sent", + "description": "The total number of emails sent for this campaign.", + "readOnly": true + }, + "send_time": { + "type": "string", + "format": "date-time", + "title": "Send Time", + "description": "The date and time a campaign was sent.", + "readOnly": true + }, + "content_type": { + "type": "string", + "title": "Content Type", + "description": "How the campaign's content is put together.", + "enum": ["template", "html", "url", "multichannel"] + }, + "needs_block_refresh": { + "type": "boolean", + "title": "Needs Block Refresh", + "description": "Determines if the campaign needs its blocks refreshed by opening the web-based campaign editor. Deprecated and will always return false.", + "readOnly": true + }, + "resendable": { + "type": "boolean", + "title": "Resendable", + "description": "Determines if the campaign qualifies to be resent to non-openers.", + "readOnly": true + }, + "recipients": { + "type": "object", + "title": "List", + "description": "List settings for the campaign.", + "properties": { + "list_id": { + "type": "string", + "title": "List ID", + "description": "The unique list id." + }, + "list_is_active": { + "type": "boolean", + "title": "List Status", + "description": "The status of the list used, namely if it's deleted or disabled.", + "readOnly": true + }, + "list_name": { + "type": "string", + "title": "List Name", + "description": "The name of the list.", + "readOnly": true + }, + "segment_text": { + "type": "string", + "title": "Segment Text", + "description": "A description of the [segment](https://mailchimp.com/help/create-and-send-to-a-segment/) used for the campaign. Formatted as a string marked up with HTML.", + "readOnly": true + }, + "recipient_count": { + "type": "integer", + "title": "Recipient Count", + "description": "Count of the recipients on the associated list. Formatted as an integer.", + "readOnly": true + }, + "segment_opts": { + "$ref": "https://us1.api.mailchimp.com/schema/3.0/Definitions/SegmentationOptions.json" + } + } + }, + "settings": { + "type": "object", + "title": "Campaign Settings", + "description": "The settings for your campaign, including subject, from name, reply-to address, and more.", + "properties": { + "subject_line": { + "type": "string", + "title": "Campaign Subject Line", + "description": "The subject line for the campaign." + }, + "preview_text": { + "type": "string", + "title": "Campaign Preview Text", + "description": "The preview text for the campaign." + }, + "title": { + "type": "string", + "title": "Campaign Title", + "description": "The title of the campaign." + }, + "from_name": { + "type": "string", + "title": "From Name", + "description": "The 'from' name on the campaign (not an email address)." + }, + "reply_to": { + "type": "string", + "title": "Reply To Address", + "description": "The reply-to email address for the campaign." + }, + "use_conversation": { + "type": "boolean", + "title": "Conversation", + "description": "Use Mailchimp Conversation feature to manage out-of-office replies." + }, + "to_name": { + "type": "string", + "title": "To Name", + "description": "The campaign's custom 'To' name. Typically the first name [merge field](https://mailchimp.com/help/getting-started-with-merge-tags/)." + }, + "folder_id": { + "type": "string", + "title": "Folder ID", + "description": "If the campaign is listed in a folder, the id for that folder." + }, + "authenticate": { + "type": "boolean", + "title": "Authentication", + "description": "Whether Mailchimp [authenticated](https://mailchimp.com/help/about-email-authentication/) the campaign. Defaults to `true`." + }, + "auto_footer": { + "type": "boolean", + "title": "Auto-Footer", + "description": "Automatically append Mailchimp's [default footer](https://mailchimp.com/help/about-campaign-footers/) to the campaign." + }, + "inline_css": { + "type": "boolean", + "title": "Inline CSS", + "description": "Automatically inline the CSS included with the campaign content." + }, + "auto_tweet": { + "type": "boolean", + "title": "Auto-Tweet", + "description": "Automatically tweet a link to the [campaign archive](https://mailchimp.com/help/about-email-campaign-archives-and-pages/) page when the campaign is sent." + }, + "auto_fb_post": { + "type": "array", + "title": "Auto Post to Facebook", + "description": "An array of [Facebook](https://mailchimp.com/help/connect-or-disconnect-the-facebook-integration/) page ids to auto-post to.", + "items": { + "type": "string" + } + }, + "fb_comments": { + "type": "boolean", + "title": "Facebook Comments", + "description": "Allows Facebook comments on the campaign (also force-enables the Campaign Archive toolbar). Defaults to `true`." + }, + "timewarp": { + "type": "boolean", + "title": "Timewarp Send", + "description": "Send this campaign using [Timewarp](https://mailchimp.com/help/use-timewarp/).", + "readOnly": true + }, + "template_id": { + "type": "integer", + "title": "Template ID", + "description": "The id for the template used in this campaign.", + "readOnly": false + }, + "drag_and_drop": { + "type": "boolean", + "title": "Drag And Drop Campaign", + "description": "Whether the campaign uses the drag-and-drop editor.", + "readOnly": true + } + } + }, + "variate_settings": { + "type": "object", + "title": "A/B Test Options", + "description": "The settings specific to A/B test campaigns.", + "properties": { + "winning_combination_id": { + "type": "string", + "title": "Winning Combination ID", + "description": "ID for the winning combination.", + "readOnly": true + }, + "winning_campaign_id": { + "type": "string", + "title": "Winning Campaign ID", + "description": "ID of the campaign that was sent to the remaining recipients based on the winning combination.", + "readOnly": true + }, + "winner_criteria": { + "type": "string", + "title": "Winning Criteria", + "description": "The combination that performs the best. This may be determined automatically by click rate, open rate, or total revenue -- or you may choose manually based on the reporting data you find the most valuable. For Multivariate Campaigns testing send_time, winner_criteria is ignored. For Multivariate Campaigns with 'manual' as the winner_criteria, the winner must be chosen in the Mailchimp web application.", + "enum": ["opens", "clicks", "manual", "total_revenue"] + }, + "wait_time": { + "type": "integer", + "title": "Wait Time", + "description": "The number of minutes to wait before choosing the winning campaign. The value of wait_time must be greater than 0 and in whole hours, specified in minutes." + }, + "test_size": { + "type": "integer", + "title": "Test Size", + "description": "The percentage of recipients to send the test combinations to, must be a value between 10 and 100." + }, + "subject_lines": { + "type": "array", + "title": "Subject Lines", + "description": "The possible subject lines to test. If no subject lines are provided, settings.subject_line will be used.", + "items": { + "type": "string" + } + }, + "send_times": { + "type": "array", + "title": "Send Times", + "description": "The possible send times to test. The times provided should be in the format YYYY-MM-DD HH:MM:SS. If send_times are provided to test, the test_size will be set to 100% and winner_criteria will be ignored.", + "items": { + "type": "string", + "format": "date-time" + } + }, + "from_names": { + "type": "array", + "title": "From Names", + "description": "The possible from names. The number of from_names provided must match the number of reply_to_addresses. If no from_names are provided, settings.from_name will be used.", + "items": { + "type": "string" + } + }, + "reply_to_addresses": { + "type": "array", + "title": "Reply To Addresses", + "description": "The possible reply-to addresses. The number of reply_to_addresses provided must match the number of from_names. If no reply_to_addresses are provided, settings.reply_to will be used.", + "items": { + "type": "string" + } + }, + "contents": { + "type": "array", + "title": "Content Descriptions", + "description": "Descriptions of possible email contents. To set campaign contents, make a PUT request to /campaigns/{campaign_id}/content with the field 'variate_contents'.", + "items": { + "type": "string" + }, + "readOnly": true + }, + "combinations": { + "type": "array", + "title": "Combinations", + "description": "Combinations of possible variables used to build emails.", + "readOnly": true, + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "title": "ID", + "description": "Unique ID for the combination." + }, + "subject_line": { + "type": "integer", + "title": "Subject Line", + "description": "The index of `variate_settings.subject_lines` used." + }, + "send_time": { + "type": "integer", + "title": "Send Time", + "description": "The index of `variate_settings.send_times` used." + }, + "from_name": { + "type": "integer", + "title": "From Name", + "description": "The index of `variate_settings.from_names` used." + }, + "reply_to": { + "type": "integer", + "title": "Reply To", + "description": "The index of `variate_settings.reply_to_addresses` used." + }, + "content_description": { + "type": "integer", + "title": "Content Description", + "description": "The index of `variate_settings.contents` used." + }, + "recipients": { + "type": "integer", + "title": "Recipients", + "description": "The number of recipients for this combination." + } + } + } + } + } + }, + "tracking": { + "type": "object", + "title": "Campaign Tracking Options", + "description": "The tracking options for a campaign.", + "properties": { + "opens": { + "type": "boolean", + "title": "Opens", + "description": "Whether to [track opens](https://mailchimp.com/help/about-open-tracking/). Defaults to `true`. Cannot be set to false for variate campaigns." + }, + "html_clicks": { + "type": "boolean", + "title": "HTML Click Tracking", + "description": "Whether to [track clicks](https://mailchimp.com/help/enable-and-view-click-tracking/) in the HTML version of the campaign. Defaults to `true`. Cannot be set to false for variate campaigns." + }, + "text_clicks": { + "type": "boolean", + "title": "Plain-Text Click Tracking", + "description": "Whether to [track clicks](https://mailchimp.com/help/enable-and-view-click-tracking/) in the plain-text version of the campaign. Defaults to `true`. Cannot be set to false for variate campaigns." + }, + "goal_tracking": { + "type": "boolean", + "title": "Mailchimp Goal Tracking", + "description": "Whether to enable [Goal](https://mailchimp.com/help/about-connected-sites/) tracking." + }, + "ecomm360": { + "type": "boolean", + "title": "E-commerce Tracking", + "description": "Whether to enable [eCommerce360](https://mailchimp.com/help/connect-your-online-store-to-mailchimp/) tracking." + }, + "google_analytics": { + "type": "string", + "title": "Google Analytics Tracking", + "description": "The custom slug for [Google Analytics](https://mailchimp.com/help/integrate-google-analytics-with-mailchimp/) tracking (max of 50 bytes)." + }, + "clicktale": { + "type": "string", + "title": "ClickTale Analytics Tracking", + "description": "The custom slug for [ClickTale](https://mailchimp.com/help/additional-tracking-options-for-campaigns/) tracking (max of 50 bytes)." + }, + "salesforce": { + "type": "object", + "title": "Salesforce CRM Tracking", + "description": "Salesforce tracking options for a campaign. Must be using Mailchimp's built-in [Salesforce integration](https://mailchimp.com/help/integrate-salesforce-with-mailchimp/).", + "properties": { + "campaign": { + "type": "boolean", + "title": "Salesforce Campaign", + "description": "Create a campaign in a connected Salesforce account." + }, + "notes": { + "type": "boolean", + "title": "Salesforce Note", + "description": "Update contact notes for a campaign based on subscriber email addresses." + } + } + }, + "capsule": { + "type": "object", + "title": "Capsule CRM Tracking", + "description": "Capsule tracking options for a campaign. Must be using Mailchimp's built-in Capsule integration.", + "properties": { + "notes": { + "type": "boolean", + "title": "Capsule Note", + "description": "Update contact notes for a campaign based on subscriber email addresses." + } + } + } + } + }, + "rss_opts": { + "type": "object", + "title": "RSS Options", + "description": "[RSS](https://mailchimp.com/help/share-your-blog-posts-with-mailchimp/) options for a campaign.", + "properties": { + "feed_url": { + "type": "string", + "title": "Feed URL", + "format": "uri", + "description": "The URL for the RSS feed." + }, + "frequency": { + "type": "string", + "title": "Frequency", + "description": "The frequency of the RSS Campaign.", + "enum": ["daily", "weekly", "monthly"] + }, + "schedule": { + "type": "object", + "title": "Sending Schedule", + "description": "The schedule for sending the RSS Campaign.", + "properties": { + "hour": { + "type": "integer", + "minimum": 0, + "maximum": 23, + "title": "Sending Hour", + "description": "The hour to send the campaign in local time. Acceptable hours are 0-23. For example, '4' would be 4am in [your account's default time zone](https://mailchimp.com/help/set-account-defaults/)." + }, + "daily_send": { + "type": "object", + "title": "Daily Sending Days", + "description": "The days of the week to send a daily RSS Campaign.", + "properties": { + "sunday": { + "type": "boolean", + "title": "Sunday", + "description": "Sends the daily RSS Campaign on Sundays." + }, + "monday": { + "type": "boolean", + "title": "Monday", + "description": "Sends the daily RSS Campaign on Mondays." + }, + "tuesday": { + "type": "boolean", + "title": "tuesday", + "description": "Sends the daily RSS Campaign on Tuesdays." + }, + "wednesday": { + "type": "boolean", + "title": "Monday", + "description": "Sends the daily RSS Campaign on Wednesdays." + }, + "thursday": { + "type": "boolean", + "title": "Thursday", + "description": "Sends the daily RSS Campaign on Thursdays." + }, + "friday": { + "type": "boolean", + "title": "Friday", + "description": "Sends the daily RSS Campaign on Fridays." + }, + "saturday": { + "type": "boolean", + "title": "Saturday", + "description": "Sends the daily RSS Campaign on Saturdays." + } + } + }, + "weekly_send_day": { + "type": "string", + "enum": [ + "sunday", + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday" + ], + "title": "Weekly Sending Day", + "description": "The day of the week to send a weekly RSS Campaign." + }, + "monthly_send_date": { + "type": "number", + "minimum": 0, + "maximum": 31, + "title": "Monthly Sending Day", + "description": "The day of the month to send a monthly RSS Campaign. Acceptable days are 0-31, where '0' is always the last day of a month. Months with fewer than the selected number of days will not have an RSS campaign sent out that day. For example, RSS Campaigns set to send on the 30th will not go out in February." + } + } + }, + "last_sent": { + "type": "string", + "format": "date-time", + "title": "Last Sent", + "description": "The date the campaign was last sent.", + "readOnly": true + }, + "constrain_rss_img": { + "type": "boolean", + "title": "Constrain RSS Images", + "description": "Whether to add CSS to images in the RSS feed to constrain their width in campaigns." + } + } + }, + "ab_split_opts": { + "type": "object", + "title": "A/B Testing Options", + "description": "[A/B Testing](https://mailchimp.com/help/about-ab-testing-campaigns/) options for a campaign.", + "readOnly": true, + "properties": { + "split_test": { + "type": "string", + "title": "Split Test", + "description": "The type of AB split to run.", + "enum": ["subject", "from_name", "schedule"] + }, + "pick_winner": { + "type": "string", + "title": "Pick Winner", + "description": "How we should evaluate a winner. Based on 'opens', 'clicks', or 'manual'.", + "enum": ["opens", "clicks", "manual"] + }, + "wait_units": { + "type": "string", + "title": "Wait Time", + "description": "How unit of time for measuring the winner ('hours' or 'days'). This cannot be changed after a campaign is sent.", + "enum": ["hours", "days"] + }, + "wait_time": { + "type": "integer", + "title": "Wait Time", + "description": "The amount of time to wait before picking a winner. This cannot be changed after a campaign is sent." + }, + "split_size": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "title": "Split Size", + "description": "The size of the split groups. Campaigns split based on 'schedule' are forced to have a 50/50 split. Valid split integers are between 1-50." + }, + "from_name_a": { + "type": "string", + "title": "From Name Group A", + "description": "For campaigns split on 'From Name', the name for Group A." + }, + "from_name_b": { + "type": "string", + "title": "From Name Group B", + "description": "For campaigns split on 'From Name', the name for Group B." + }, + "reply_email_a": { + "type": "string", + "title": "Reply Email Group A", + "description": "For campaigns split on 'From Name', the reply-to address for Group A." + }, + "reply_email_b": { + "type": "string", + "title": "Reply Email Group B", + "description": "For campaigns split on 'From Name', the reply-to address for Group B." + }, + "subject_a": { + "type": "string", + "title": "Subject Line Group A", + "description": "For campaigns split on 'Subject Line', the subject line for Group A." + }, + "subject_b": { + "type": "string", + "title": "Subject Line Group B", + "description": "For campaigns split on 'Subject Line', the subject line for Group B." + }, + "send_time_a": { + "type": "string", + "format": "date-time", + "title": "Send Time Group A", + "description": "The send time for Group A." + }, + "send_time_b": { + "type": "string", + "format": "date-time", + "title": "Send Time Group B", + "description": "The send time for Group B." + }, + "send_time_winner": { + "type": "string", + "title": "Send Time Winner", + "description": "The send time for the winning version." + } + } + }, + "social_card": { + "type": "object", + "title": "Campaign Social Card", + "description": "The preview for the campaign, rendered by social networks like Facebook and Twitter. [Learn more](https://mailchimp.com/help/enable-and-customize-social-cards/).", + "properties": { + "image_url": { + "type": "string", + "title": "Image URL", + "description": "The url for the header image for the card." + }, + "description": { + "type": "string", + "title": "Campaign Description", + "description": "A short summary of the campaign to display." + }, + "title": { + "type": "string", + "title": "Title", + "description": "The title for the card. Typically the subject line of the campaign." + } + } + }, + "report_summary": { + "type": "object", + "title": "Campaign Report Summary", + "description": "For sent campaigns, a summary of opens, clicks, and e-commerce data.", + "properties": { + "opens": { + "type": "integer", + "title": "Automation Opens", + "description": "The total number of opens for a campaign.", + "readOnly": true + }, + "unique_opens": { + "type": "integer", + "title": "Unique Opens", + "description": "The number of unique opens.", + "readOnly": true + }, + "open_rate": { + "type": "number", + "title": "Open Rate", + "description": "The number of unique opens divided by the total number of successful deliveries.", + "readOnly": true + }, + "clicks": { + "type": "integer", + "title": "Total Clicks", + "description": "The total number of clicks for an campaign.", + "readOnly": true + }, + "subscriber_clicks": { + "type": "integer", + "title": "Unique Subscriber Clicks", + "description": "The number of unique clicks.", + "readOnly": true + }, + "click_rate": { + "type": "number", + "title": "Click Rate", + "description": "The number of unique clicks divided by the total number of successful deliveries.", + "readOnly": true + }, + "ecommerce": { + "type": "object", + "title": "E-Commerce Report", + "description": "E-Commerce stats for a campaign.", + "properties": { + "total_orders": { + "type": "integer", + "title": "Total Orders", + "description": "The total orders for a campaign.", + "readOnly": true + }, + "total_spent": { + "type": "number", + "title": "Total Spent", + "description": "The total spent for a campaign. Calculated as the sum of all order totals with no deductions.", + "readOnly": true + }, + "total_revenue": { + "type": "number", + "title": "Total Revenue", + "description": "The total revenue for a campaign. Calculated as the sum of all order totals minus shipping and tax totals.", + "readOnly": true + } + } + } + } + }, + "delivery_status": { + "type": "object", + "title": "Campaign Delivery Status", + "description": "Updates on campaigns in the process of sending.", + "properties": { + "enabled": { + "type": "boolean", + "title": "Delivery Status Enabled", + "description": "Whether Campaign Delivery Status is enabled for this account and campaign.", + "readOnly": true + }, + "can_cancel": { + "type": "boolean", + "title": "Campaign Cancelable", + "description": "Whether a campaign send can be canceled.", + "readOnly": true + }, + "status": { + "type": "string", + "title": "Campaign Delivery Status", + "description": "The current state of a campaign delivery.", + "enum": ["delivering", "delivered", "canceling", "canceled"], + "readOnly": true + }, + "emails_sent": { + "type": "integer", + "title": "Emails Sent", + "description": "The total number of emails confirmed sent for this campaign so far.", + "readOnly": true + }, + "emails_canceled": { + "type": "integer", + "title": "Emails Canceled", + "description": "The total number of emails canceled for this campaign.", + "readOnly": true + } + } + } + } + }, + "supported_sync_modes": ["full_refresh", "incremental"], + "source_defined_cursor": true, + "default_cursor_field": ["create_time"] + }, + "sync_mode": "incremental", + "cursor_field": ["create_time"], + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "lists", + "json_schema": { + "type": "object", + "title": "Subscriber List", + "description": "Information about a specific list.", + "properties": { + "id": { + "type": "string", + "title": "List ID", + "description": "A string that uniquely identifies this list.", + "readOnly": true + }, + "web_id": { + "type": "integer", + "title": "List Web ID", + "description": "The ID used in the Mailchimp web application. View this list in your Mailchimp account at `https://{dc}.admin.mailchimp.com/lists/members/?id={web_id}`.", + "readOnly": true + }, + "name": { + "type": "string", + "title": "List Name", + "description": "The name of the list." + }, + "contact": { + "type": "object", + "title": "List Contact", + "description": "[Contact information displayed in campaign footers](https://mailchimp.com/help/about-campaign-footers/) to comply with international spam laws.", + "properties": { + "company": { + "type": "string", + "title": "Company Name", + "description": "The company name for the list." + }, + "address1": { + "type": "string", + "title": "Address", + "description": "The street address for the list contact." + }, + "address2": { + "type": "string", + "title": "Address", + "description": "The street address for the list contact." + }, + "city": { + "type": "string", + "title": "City", + "description": "The city for the list contact." + }, + "state": { + "type": "string", + "title": "State", + "description": "The state for the list contact." + }, + "zip": { + "type": "string", + "title": "Postal Code", + "description": "The postal or zip code for the list contact." + }, + "country": { + "type": "string", + "title": "Country Code", + "description": "A two-character ISO3166 country code. Defaults to US if invalid." + }, + "phone": { + "type": "string", + "title": "Phone Number", + "description": "The phone number for the list contact." + } + } + }, + "permission_reminder": { + "type": "string", + "title": "Permission Reminder", + "description": "The [permission reminder](https://mailchimp.com/help/edit-the-permission-reminder/) for the list." + }, + "use_archive_bar": { + "type": "boolean", + "title": "Use Archive Bar", + "description": "Whether campaigns for this list use the [Archive Bar](https://mailchimp.com/help/about-email-campaign-archives-and-pages/) in archives by default.", + "default": false + }, + "campaign_defaults": { + "type": "object", + "title": "Campaign Defaults", + "description": "[Default values for campaigns](https://mailchimp.com/help/edit-your-emails-subject-preview-text-from-name-or-from-email-address/) created for this list.", + "properties": { + "from_name": { + "type": "string", + "title": "Sender's Name", + "description": "The default from name for campaigns sent to this list." + }, + "from_email": { + "type": "string", + "title": "Sender's Email Address", + "description": "The default from email for campaigns sent to this list." + }, + "subject": { + "type": "string", + "title": "Subject", + "description": "The default subject line for campaigns sent to this list." + }, + "language": { + "type": "string", + "title": "Language", + "description": "The default language for this lists's forms." + } + } + }, + "notify_on_subscribe": { + "type": "string", + "title": "Notify on Subscribe", + "description": "The email address to send [subscribe notifications](https://mailchimp.com/help/change-subscribe-and-unsubscribe-notifications/) to.", + "default": false + }, + "notify_on_unsubscribe": { + "type": "string", + "title": "Notify on Unsubscribe", + "description": "The email address to send [unsubscribe notifications](https://mailchimp.com/help/change-subscribe-and-unsubscribe-notifications/) to.", + "default": false + }, + "date_created": { + "type": "string", + "title": "Creation Date", + "description": "The date and time that this list was created in ISO 8601 format.", + "format": "date-time", + "readOnly": true + }, + "list_rating": { + "type": "integer", + "title": "List Rating", + "description": "An auto-generated activity score for the list (0-5).", + "readOnly": true + }, + "email_type_option": { + "type": "boolean", + "title": "Email Type Option", + "description": "Whether the list supports [multiple formats for emails](https://mailchimp.com/help/change-list-name-and-defaults/). When set to `true`, subscribers can choose whether they want to receive HTML or plain-text emails. When set to `false`, subscribers will receive HTML emails, with a plain-text alternative backup." + }, + "subscribe_url_short": { + "type": "string", + "title": "Subscribe URL Short", + "description": "Our [EepURL shortened](https://mailchimp.com/help/share-your-signup-form/) version of this list's subscribe form.", + "readOnly": true + }, + "subscribe_url_long": { + "type": "string", + "title": "Subscribe URL Long", + "description": "The full version of this list's subscribe form (host will vary).", + "readOnly": true + }, + "beamer_address": { + "type": "string", + "title": "Beamer Address", + "description": "The list's [Email Beamer](https://mailchimp.com/help/use-email-beamer-to-create-a-campaign/) address.", + "readOnly": true + }, + "visibility": { + "type": "string", + "title": "Visibility", + "enum": ["pub", "prv"], + "description": "Whether this list is [public or private](https://mailchimp.com/help/about-list-publicity/)." + }, + "double_optin": { + "type": "boolean", + "title": "Double Opt In", + "description": "Whether or not to require the subscriber to confirm subscription via email.", + "default": false + }, + "has_welcome": { + "type": "boolean", + "title": "Has Welcome", + "description": "Whether or not this list has a welcome automation connected. Welcome Automations: welcomeSeries, singleWelcome, emailFollowup.", + "default": false, + "example": false + }, + "marketing_permissions": { + "type": "boolean", + "title": "Marketing Permissions", + "description": "Whether or not the list has marketing permissions (eg. GDPR) enabled.", + "default": false + }, + "modules": { + "type": "array", + "title": "Modules", + "description": "Any list-specific modules installed for this list.", + "items": { + "type": "string" + }, + "readOnly": true + }, + "stats": { + "type": "object", + "title": "Statistics", + "description": "Stats for the list. Many of these are cached for at least five minutes.", + "readOnly": true, + "properties": { + "member_count": { + "type": "integer", + "title": "Member Count", + "description": "The number of active members in the list.", + "readOnly": true + }, + "total_contacts": { + "type": "integer", + "title": "Total Contacts", + "description": "The number of contacts in the list, including subscribed, unsubscribed, pending, cleaned, deleted, transactional, and those that need to be reconfirmed.", + "readOnly": true + }, + "unsubscribe_count": { + "type": "integer", + "title": "Unsubscribe Count", + "description": "The number of members who have unsubscribed from the list.", + "readOnly": true + }, + "cleaned_count": { + "type": "integer", + "title": "Cleaned Count", + "description": "The number of members cleaned from the list.", + "readOnly": true + }, + "member_count_since_send": { + "type": "integer", + "title": "Member Count Since Send", + "description": "The number of active members in the list since the last campaign was sent.", + "readOnly": true + }, + "unsubscribe_count_since_send": { + "type": "integer", + "title": "Unsubscribe Count Since Send", + "description": "The number of members who have unsubscribed since the last campaign was sent.", + "readOnly": true + }, + "cleaned_count_since_send": { + "type": "integer", + "title": "Cleaned Count Since Send", + "description": "The number of members cleaned from the list since the last campaign was sent.", + "readOnly": true + }, + "campaign_count": { + "type": "integer", + "title": "Campaign Count", + "description": "The number of campaigns in any status that use this list.", + "readOnly": true + }, + "campaign_last_sent": { + "type": "string", + "format": "date-time", + "title": "Campaign Last Sent", + "description": "The date and time the last campaign was sent to this list in ISO 8601 format. This is updated when a campaign is sent to 10 or more recipients.", + "readOnly": true + }, + "merge_field_count": { + "type": "integer", + "title": "Merge Var Count", + "description": "The number of merge vars for this list (not EMAIL, which is required).", + "readOnly": true + }, + "avg_sub_rate": { + "type": "number", + "title": "Average Subscription Rate", + "description": "The average number of subscriptions per month for the list (not returned if we haven't calculated it yet).", + "readOnly": true + }, + "avg_unsub_rate": { + "type": "number", + "title": "Average Unsubscription Rate", + "description": "The average number of unsubscriptions per month for the list (not returned if we haven't calculated it yet).", + "readOnly": true + }, + "target_sub_rate": { + "type": "number", + "title": "Average Subscription Rate", + "description": "The target number of subscriptions per month for the list to keep it growing (not returned if we haven't calculated it yet).", + "readOnly": true + }, + "open_rate": { + "type": "number", + "title": "Open Rate", + "description": "The average open rate (a percentage represented as a number between 0 and 100) per campaign for the list (not returned if we haven't calculated it yet).", + "readOnly": true + }, + "click_rate": { + "type": "number", + "title": "Click Rate", + "description": "The average click rate (a percentage represented as a number between 0 and 100) per campaign for the list (not returned if we haven't calculated it yet).", + "readOnly": true + }, + "last_sub_date": { + "type": "string", + "format": "date-time", + "title": "Date of Last List Subscribe", + "description": "The date and time of the last time someone subscribed to this list in ISO 8601 format.", + "readOnly": true + }, + "last_unsub_date": { + "type": "string", + "format": "date-time", + "title": "Date of Last List Unsubscribe", + "description": "The date and time of the last time someone unsubscribed from this list in ISO 8601 format.", + "readOnly": true + } + } + } + } + }, + "supported_sync_modes": ["full_refresh", "incremental"], + "source_defined_cursor": true, + "default_cursor_field": ["date_created"] + }, + "sync_mode": "incremental", + "cursor_field": ["date_created"], + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "email_activity", + "json_schema": { + "type": "object", + "title": "Email Activity", + "description": "A list of member's subscriber activity in a specific campaign.", + "properties": { + "emails": { + "type": "object", + "title": "List members", + "description": "An array of members that were sent the campaign.", + "properties": { + "campaign_id": { + "type": "string", + "title": "The unique id for the campaign.", + "description": "The unique id for the campaign." + }, + "list_id": { + "type": "string", + "title": "The unique id for the list.", + "description": "The unique id for the list." + }, + "list_is_active": { + "type": "boolean", + "title": "The status of the list used.", + "description": "The status of the list used, namely if it's deleted or disabled." + }, + "email_id": { + "type": "string", + "title": "email MD5 hash.", + "description": "The MD5 hash of the lowercase version of the list member's email address." + }, + "email_address": { + "type": "string", + "title": "Email address for a subscriber.", + "description": "Email address for a subscriber." + }, + "action": { + "type": "string", + "title": "action", + "enum": ["open", "click", "bounce"], + "description": "One of the following actions: 'open', 'click', or 'bounce'" + }, + "type": { + "type": "string", + "title": "Type", + "enum": ["hard", "soft"], + "description": "If the action is a 'bounce', the type of bounce received: 'hard', 'soft'." + }, + "timestamp": { + "type": "string", + "title": "Action date and time", + "description": "The date and time recorded for the action in ISO 8601 format.", + "format": "date-time" + }, + "url": { + "type": "string", + "title": "The IP address.", + "description": "The IP address recorded for the action." + }, + "ip": { + "type": "string", + "title": "Action ip address", + "description": "The IP address recorded for the action." + } + } + }, + "campaign_id": { + "type": "string", + "title": "Campaign ID", + "description": "The unique id for the sent campaign." + }, + "total_items": { + "type": "integer", + "title": "Total amount of items", + "description": "The total number of items matching the query regardless of pagination." + } + } + }, + "supported_sync_modes": ["incremental", "full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["timestamp"] + }, + "sync_mode": "incremental", + "cursor_field": ["timestamp"], + "destination_sync_mode": "append" + } + ] +} diff --git a/airbyte-integrations/connectors/source-mailchimp/integration_tests/invalid_config.json b/airbyte-integrations/connectors/source-mailchimp/integration_tests/invalid_config.json new file mode 100644 index 0000000000000..308434ab6d6ef --- /dev/null +++ b/airbyte-integrations/connectors/source-mailchimp/integration_tests/invalid_config.json @@ -0,0 +1,4 @@ +{ + "apikey": "api-key-awesome", + "username": "fakemaster@silly.com" +} diff --git a/airbyte-integrations/connectors/source-mailchimp/integration_tests/state.json b/airbyte-integrations/connectors/source-mailchimp/integration_tests/state.json new file mode 100644 index 0000000000000..7fe6fa04915b3 --- /dev/null +++ b/airbyte-integrations/connectors/source-mailchimp/integration_tests/state.json @@ -0,0 +1,7 @@ +{ + "campaigns": { "create_time": "2020-11-23T05:42:11+00:00" }, + "lists": { "date_created": "2020-09-25T04:47:31+00:00" }, + "email_activity": { + "49d68626f3": { "timestamp": "2020-11-23T05:42:10+00:00" } + } +} diff --git a/airbyte-integrations/connectors/source-mailchimp/main_dev.py b/airbyte-integrations/connectors/source-mailchimp/main.py similarity index 96% rename from airbyte-integrations/connectors/source-mailchimp/main_dev.py rename to airbyte-integrations/connectors/source-mailchimp/main.py index 434a0fdbc2fe5..7d714c1708274 100644 --- a/airbyte-integrations/connectors/source-mailchimp/main_dev.py +++ b/airbyte-integrations/connectors/source-mailchimp/main.py @@ -25,7 +25,7 @@ import sys -from base_python.entrypoint import launch +from airbyte_cdk.entrypoint import launch from source_mailchimp import SourceMailchimp if __name__ == "__main__": diff --git a/airbyte-integrations/connectors/source-mailchimp/requirements.txt b/airbyte-integrations/connectors/source-mailchimp/requirements.txt index dd447512e620a..e74f41a28ce1b 100644 --- a/airbyte-integrations/connectors/source-mailchimp/requirements.txt +++ b/airbyte-integrations/connectors/source-mailchimp/requirements.txt @@ -1,4 +1,5 @@ # This file is autogenerated -- only edit if you know what you are doing. Use setup.py for declaring dependencies. -e ../../bases/airbyte-protocol -e ../../bases/base-python +-e ../../bases/source-acceptance-test -e . diff --git a/airbyte-integrations/connectors/source-mailchimp/sample_files/configured_catalog.json b/airbyte-integrations/connectors/source-mailchimp/sample_files/configured_catalog.json index fd6e073f9d6db..8685a0a679b68 100644 --- a/airbyte-integrations/connectors/source-mailchimp/sample_files/configured_catalog.json +++ b/airbyte-integrations/connectors/source-mailchimp/sample_files/configured_catalog.json @@ -2,7 +2,7 @@ "streams": [ { "stream": { - "name": "Campaigns", + "name": "campaigns", "json_schema": { "type": "object", "title": "Campaign", @@ -733,58 +733,6 @@ "readOnly": true } } - }, - "_links": { - "title": "Links", - "description": "A list of link types and descriptions for the API schema documents.", - "type": "array", - "items": { - "type": "object", - "title": "Resource Link", - "description": "This object represents a link from the resource where it is found to another resource or action that may be performed.", - "properties": { - "rel": { - "type": "string", - "title": "Rel", - "description": "As with an HTML 'rel' attribute, this describes the type of link.", - "readOnly": true - }, - "href": { - "type": "string", - "title": "Href", - "description": "This property contains a fully-qualified URL that can be called to retrieve the linked resource or perform the linked action.", - "readOnly": true - }, - "method": { - "type": "string", - "title": "Method", - "description": "The HTTP method that should be used when accessing the URL defined in 'href'.", - "enum": [ - "GET", - "POST", - "PUT", - "PATCH", - "DELETE", - "OPTIONS", - "HEAD" - ], - "readOnly": true - }, - "targetSchema": { - "type": "string", - "title": "Target Schema", - "description": "For GETs, this is a URL representing the schema that the response should conform to.", - "readOnly": true - }, - "schema": { - "type": "string", - "title": "Schema", - "description": "For HTTP methods that can receive bodies (POST and PUT), this is a URL representing the schema that the body should conform to.", - "readOnly": true - } - } - }, - "readOnly": true } } }, @@ -798,7 +746,7 @@ }, { "stream": { - "name": "Lists", + "name": "lists", "json_schema": { "type": "object", "title": "Subscriber List", @@ -1100,58 +1048,6 @@ "readOnly": true } } - }, - "_links": { - "title": "Links", - "description": "A list of link types and descriptions for the API schema documents.", - "type": "array", - "items": { - "type": "object", - "title": "Resource Link", - "description": "This object represents a link from the resource where it is found to another resource or action that may be performed.", - "properties": { - "rel": { - "type": "string", - "title": "Rel", - "description": "As with an HTML 'rel' attribute, this describes the type of link.", - "readOnly": true - }, - "href": { - "type": "string", - "title": "Href", - "description": "This property contains a fully-qualified URL that can be called to retrieve the linked resource or perform the linked action.", - "readOnly": true - }, - "method": { - "type": "string", - "title": "Method", - "description": "The HTTP method that should be used when accessing the URL defined in 'href'.", - "enum": [ - "GET", - "POST", - "PUT", - "PATCH", - "DELETE", - "OPTIONS", - "HEAD" - ], - "readOnly": true - }, - "targetSchema": { - "type": "string", - "title": "Target Schema", - "description": "For GETs, this is a URL representing the schema that the response should conform to.", - "readOnly": true - }, - "schema": { - "type": "string", - "title": "Schema", - "description": "For HTTP methods that can receive bodies (POST and PUT), this is a URL representing the schema that the body should conform to.", - "readOnly": true - } - } - }, - "readOnly": true } } }, @@ -1162,6 +1058,94 @@ "sync_mode": "incremental", "cursor_field": ["date_created"], "destination_sync_mode": "append" + }, + { + "stream": { + "name": "email_activity", + "json_schema": { + "type": "object", + "title": "Email Activity", + "description": "A list of member's subscriber activity in a specific campaign.", + "properties": { + "emails": { + "type": "object", + "title": "List members", + "description": "An array of members that were sent the campaign.", + "properties": { + "campaign_id": { + "type": "string", + "title": "The unique id for the campaign.", + "description": "The unique id for the campaign." + }, + "list_id": { + "type": "string", + "title": "The unique id for the list.", + "description": "The unique id for the list." + }, + "list_is_active": { + "type": "boolean", + "title": "The status of the list used.", + "description": "The status of the list used, namely if it's deleted or disabled." + }, + "email_id": { + "type": "string", + "title": "email MD5 hash.", + "description": "The MD5 hash of the lowercase version of the list member's email address." + }, + "email_address": { + "type": "string", + "title": "Email address for a subscriber.", + "description": "Email address for a subscriber." + }, + "action": { + "type": "string", + "title": "action", + "enum": ["open", "click", "bounce"], + "description": "One of the following actions: 'open', 'click', or 'bounce'" + }, + "type": { + "type": "string", + "title": "Type", + "enum": ["hard", "soft"], + "description": "If the action is a 'bounce', the type of bounce received: 'hard', 'soft'." + }, + "timestamp": { + "type": "string", + "title": "Action date and time", + "description": "The date and time recorded for the action in ISO 8601 format.", + "format": "date-time" + }, + "url": { + "type": "string", + "title": "The IP address.", + "description": "The IP address recorded for the action." + }, + "ip": { + "type": "string", + "title": "Action ip address", + "description": "The IP address recorded for the action." + } + } + }, + "campaign_id": { + "type": "string", + "title": "Campaign ID", + "description": "The unique id for the sent campaign." + }, + "total_items": { + "type": "integer", + "title": "Total amount of items", + "description": "The total number of items matching the query regardless of pagination." + } + } + }, + "supported_sync_modes": ["incremental", "full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["timestamp"] + }, + "sync_mode": "incremental", + "cursor_field": ["timestamp"], + "destination_sync_mode": "append" } ] } diff --git a/airbyte-integrations/connectors/source-mailchimp/sample_files/state.json b/airbyte-integrations/connectors/source-mailchimp/sample_files/state.json index a0db96f8d241a..7fe6fa04915b3 100644 --- a/airbyte-integrations/connectors/source-mailchimp/sample_files/state.json +++ b/airbyte-integrations/connectors/source-mailchimp/sample_files/state.json @@ -1,4 +1,7 @@ { - "Campaigns": { "create_time": "2020-11-23T05:42:11+00:00" }, - "Lists": { "date_created": "2020-09-25T04:47:31+00:00" } + "campaigns": { "create_time": "2020-11-23T05:42:11+00:00" }, + "lists": { "date_created": "2020-09-25T04:47:31+00:00" }, + "email_activity": { + "49d68626f3": { "timestamp": "2020-11-23T05:42:10+00:00" } + } } diff --git a/airbyte-integrations/connectors/source-mailchimp/setup.py b/airbyte-integrations/connectors/source-mailchimp/setup.py index 0a177b4a199ce..4f27187a57371 100644 --- a/airbyte-integrations/connectors/source-mailchimp/setup.py +++ b/airbyte-integrations/connectors/source-mailchimp/setup.py @@ -32,12 +32,10 @@ author_email="contact@airbyte.io", packages=find_packages(), install_requires=[ - "airbyte-protocol", - "base-python", - "pydantic==1.6.2", + "airbyte-cdk~=0.1", "mailchimp3==3.0.14", - "python-dateutil==2.8.1", - "pytest==6.1.2", + "pytest~=6.1", ], package_data={"": ["*.json", "schemas/*.json"]}, + extras_require={"tests": ["pytest~=6.1"]}, ) diff --git a/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/client.py b/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/client.py deleted file mode 100644 index f22b4e66370bd..0000000000000 --- a/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/client.py +++ /dev/null @@ -1,142 +0,0 @@ -# -# 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. -# - - -import json -import pkgutil -from datetime import datetime -from typing import DefaultDict, Dict, Generator, Tuple - -from airbyte_protocol import AirbyteMessage, AirbyteRecordMessage, AirbyteStateMessage, AirbyteStream, Type -from dateutil import parser -from mailchimp3 import MailChimp -from mailchimp3.mailchimpclient import MailChimpError - -from .models import HealthCheckError - - -class Client: - PAGINATION = 100 - _CAMPAIGNS = "Campaigns" - _LISTS = "Lists" - _ENTITIES = [_CAMPAIGNS, _LISTS] - - def __init__(self, username: str, apikey: str): - self._client = MailChimp(mc_api=apikey, mc_user=username) - - def health_check(self): - try: - self._client.ping.get() - return True, None - except MailChimpError as err: - return False, HealthCheckError.parse_obj(err.args[0]) - - def get_streams(self): - streams = [] - for entity in self._ENTITIES: - raw_schema = json.loads(pkgutil.get_data(self.__class__.__module__.split(".")[0], f"schemas/{entity}.json")) - streams.append(AirbyteStream.parse_obj(raw_schema)) - return streams - - def lists(self, state: DefaultDict[str, any]) -> Generator[AirbyteMessage, None, None]: - cursor_field = "date_created" - stream_name = self._LISTS - date_created = self._get_cursor_or_none(state, stream_name, cursor_field) - default_params, max_date_created = self._get_default_params_and_cursor(cursor_field, date_created) - - offset = 0 - done = False - while not done: - params = dict(default_params, count=self.PAGINATION, offset=offset) - lists_response = self._client.lists.all(**params)["lists"] - for mc_list in lists_response: - list_created_at = parser.isoparse(mc_list[cursor_field]) - max_date_created = max(max_date_created, list_created_at) if max_date_created else list_created_at - yield self._record(stream=self._LISTS, data=mc_list) - - if max_date_created: - state[self._LISTS][cursor_field] = self._format_date_as_string(max_date_created) - yield self._state(state) - - done = len(lists_response) < self.PAGINATION - offset += self.PAGINATION - - def campaigns(self, state: DefaultDict[str, any]) -> Generator[AirbyteMessage, None, None]: - cursor_field = "create_time" - stream_name = self._CAMPAIGNS - create_time = self._get_cursor_or_none(state, stream_name, cursor_field) - default_params, max_create_time = self._get_default_params_and_cursor(cursor_field, create_time) - - offset = 0 - done = False - while not done: - params = dict(default_params, count=self.PAGINATION, offset=offset) - campaigns_response = self._client.campaigns.all(**params) - for campaign in campaigns_response["campaigns"]: - campaign_created_at = parser.isoparse(campaign[cursor_field]) - max_create_time = max(max_create_time, campaign_created_at) if max_create_time else campaign_created_at - yield self._record(stream=stream_name, data=campaign) - - if max_create_time: - state[stream_name][cursor_field] = self._format_date_as_string(max_create_time) - yield self._state(state) - - done = len(campaigns_response) < self.PAGINATION - offset += self.PAGINATION - - @staticmethod - def _get_default_params_and_cursor(cursor_field_name: str, cursor_value: str) -> Tuple[Dict[str, str], datetime]: - """ - :param cursor_field_name: - :param cursor_value: assumed to be a date represented as an ISO8601 string - :return: - """ - default_params = {"sort_field": cursor_field_name, "sort_dir": "ASC"} - if cursor_value: - default_params[f"since_{cursor_field_name}"] = cursor_value - parsed_date_cursor = parser.isoparse(cursor_value) - else: - parsed_date_cursor = None - - return default_params, parsed_date_cursor - - @staticmethod - def _format_date_as_string(d: datetime) -> str: - return d.astimezone().replace(microsecond=0).isoformat() - - @staticmethod - def _get_cursor_or_none(state: DefaultDict[str, any], stream_name: str, cursor_name: str) -> any: - if state and stream_name in state and cursor_name in state[stream_name]: - return state[stream_name][cursor_name] - else: - return None - - @staticmethod - def _record(stream: str, data: Dict[str, any]) -> AirbyteMessage: - now = int(datetime.now().timestamp()) * 1000 - return AirbyteMessage(type=Type.RECORD, record=AirbyteRecordMessage(stream=stream, data=data, emitted_at=now)) - - @staticmethod - def _state(data: Dict[str, any]): - return AirbyteMessage(type=Type.STATE, state=AirbyteStateMessage(data=data)) diff --git a/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/schemas/Campaigns.json b/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/schemas/Campaigns.json deleted file mode 100644 index 0b5fbeb490832..0000000000000 --- a/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/schemas/Campaigns.json +++ /dev/null @@ -1,791 +0,0 @@ -{ - "name": "Campaigns", - "supported_sync_modes": ["full_refresh", "incremental"], - "source_defined_cursor": true, - "default_cursor_field": ["create_time"], - "json_schema": { - "type": "object", - "title": "Campaign", - "description": "A summary of an individual campaign's settings and content.", - "properties": { - "id": { - "type": "string", - "title": "Campaign ID", - "description": "A string that uniquely identifies this campaign.", - "readOnly": true - }, - "web_id": { - "type": "integer", - "title": "Campaign Web ID", - "description": "The ID used in the Mailchimp web application. View this campaign in your Mailchimp account at `https://{dc}.admin.mailchimp.com/campaigns/show/?id={web_id}`.", - "readOnly": true - }, - "parent_campaign_id": { - "type": "string", - "title": "Parent Campaign ID", - "description": "If this campaign is the child of another campaign, this identifies the parent campaign. For Example, for RSS or Automation children.", - "readOnly": true - }, - "type": { - "$ref": "https://us1.api.mailchimp.com/schema/3.0/Definitions/CampaignType.json" - }, - "create_time": { - "type": "string", - "format": "date-time", - "title": "Create Time", - "description": "The date and time the campaign was created in ISO 8601 format.", - "readOnly": true - }, - "archive_url": { - "type": "string", - "title": "Archive URL", - "description": "The link to the campaign's archive version in ISO 8601 format.", - "readOnly": true - }, - "long_archive_url": { - "type": "string", - "title": "Long Archive URL", - "description": "The original link to the campaign's archive version.", - "readOnly": true - }, - "status": { - "$ref": "https://us1.api.mailchimp.com/schema/3.0/Definitions/CampaignStatus.json" - }, - "emails_sent": { - "type": "integer", - "title": "Emails Sent", - "description": "The total number of emails sent for this campaign.", - "readOnly": true - }, - "send_time": { - "type": "string", - "format": "date-time", - "title": "Send Time", - "description": "The date and time a campaign was sent.", - "readOnly": true - }, - "content_type": { - "type": "string", - "title": "Content Type", - "description": "How the campaign's content is put together.", - "enum": ["template", "html", "url", "multichannel"] - }, - "needs_block_refresh": { - "type": "boolean", - "title": "Needs Block Refresh", - "description": "Determines if the campaign needs its blocks refreshed by opening the web-based campaign editor. Deprecated and will always return false.", - "readOnly": true - }, - "resendable": { - "type": "boolean", - "title": "Resendable", - "description": "Determines if the campaign qualifies to be resent to non-openers.", - "readOnly": true - }, - "recipients": { - "type": "object", - "title": "List", - "description": "List settings for the campaign.", - "properties": { - "list_id": { - "type": "string", - "title": "List ID", - "description": "The unique list id." - }, - "list_is_active": { - "type": "boolean", - "title": "List Status", - "description": "The status of the list used, namely if it's deleted or disabled.", - "readOnly": true - }, - "list_name": { - "type": "string", - "title": "List Name", - "description": "The name of the list.", - "readOnly": true - }, - "segment_text": { - "type": "string", - "title": "Segment Text", - "description": "A description of the [segment](https://mailchimp.com/help/create-and-send-to-a-segment/) used for the campaign. Formatted as a string marked up with HTML.", - "readOnly": true - }, - "recipient_count": { - "type": "integer", - "title": "Recipient Count", - "description": "Count of the recipients on the associated list. Formatted as an integer.", - "readOnly": true - }, - "segment_opts": { - "$ref": "https://us1.api.mailchimp.com/schema/3.0/Definitions/SegmentationOptions.json" - } - } - }, - "settings": { - "type": "object", - "title": "Campaign Settings", - "description": "The settings for your campaign, including subject, from name, reply-to address, and more.", - "properties": { - "subject_line": { - "type": "string", - "title": "Campaign Subject Line", - "description": "The subject line for the campaign." - }, - "preview_text": { - "type": "string", - "title": "Campaign Preview Text", - "description": "The preview text for the campaign." - }, - "title": { - "type": "string", - "title": "Campaign Title", - "description": "The title of the campaign." - }, - "from_name": { - "type": "string", - "title": "From Name", - "description": "The 'from' name on the campaign (not an email address)." - }, - "reply_to": { - "type": "string", - "title": "Reply To Address", - "description": "The reply-to email address for the campaign." - }, - "use_conversation": { - "type": "boolean", - "title": "Conversation", - "description": "Use Mailchimp Conversation feature to manage out-of-office replies." - }, - "to_name": { - "type": "string", - "title": "To Name", - "description": "The campaign's custom 'To' name. Typically the first name [merge field](https://mailchimp.com/help/getting-started-with-merge-tags/)." - }, - "folder_id": { - "type": "string", - "title": "Folder ID", - "description": "If the campaign is listed in a folder, the id for that folder." - }, - "authenticate": { - "type": "boolean", - "title": "Authentication", - "description": "Whether Mailchimp [authenticated](https://mailchimp.com/help/about-email-authentication/) the campaign. Defaults to `true`." - }, - "auto_footer": { - "type": "boolean", - "title": "Auto-Footer", - "description": "Automatically append Mailchimp's [default footer](https://mailchimp.com/help/about-campaign-footers/) to the campaign." - }, - "inline_css": { - "type": "boolean", - "title": "Inline CSS", - "description": "Automatically inline the CSS included with the campaign content." - }, - "auto_tweet": { - "type": "boolean", - "title": "Auto-Tweet", - "description": "Automatically tweet a link to the [campaign archive](https://mailchimp.com/help/about-email-campaign-archives-and-pages/) page when the campaign is sent." - }, - "auto_fb_post": { - "type": "array", - "title": "Auto Post to Facebook", - "description": "An array of [Facebook](https://mailchimp.com/help/connect-or-disconnect-the-facebook-integration/) page ids to auto-post to.", - "items": { - "type": "string" - } - }, - "fb_comments": { - "type": "boolean", - "title": "Facebook Comments", - "description": "Allows Facebook comments on the campaign (also force-enables the Campaign Archive toolbar). Defaults to `true`." - }, - "timewarp": { - "type": "boolean", - "title": "Timewarp Send", - "description": "Send this campaign using [Timewarp](https://mailchimp.com/help/use-timewarp/).", - "readOnly": true - }, - "template_id": { - "type": "integer", - "title": "Template ID", - "description": "The id for the template used in this campaign.", - "readOnly": false - }, - "drag_and_drop": { - "type": "boolean", - "title": "Drag And Drop Campaign", - "description": "Whether the campaign uses the drag-and-drop editor.", - "readOnly": true - } - } - }, - "variate_settings": { - "type": "object", - "title": "A/B Test Options", - "description": "The settings specific to A/B test campaigns.", - "properties": { - "winning_combination_id": { - "type": "string", - "title": "Winning Combination ID", - "description": "ID for the winning combination.", - "readOnly": true - }, - "winning_campaign_id": { - "type": "string", - "title": "Winning Campaign ID", - "description": "ID of the campaign that was sent to the remaining recipients based on the winning combination.", - "readOnly": true - }, - "winner_criteria": { - "type": "string", - "title": "Winning Criteria", - "description": "The combination that performs the best. This may be determined automatically by click rate, open rate, or total revenue -- or you may choose manually based on the reporting data you find the most valuable. For Multivariate Campaigns testing send_time, winner_criteria is ignored. For Multivariate Campaigns with 'manual' as the winner_criteria, the winner must be chosen in the Mailchimp web application.", - "enum": ["opens", "clicks", "manual", "total_revenue"] - }, - "wait_time": { - "type": "integer", - "title": "Wait Time", - "description": "The number of minutes to wait before choosing the winning campaign. The value of wait_time must be greater than 0 and in whole hours, specified in minutes." - }, - "test_size": { - "type": "integer", - "title": "Test Size", - "description": "The percentage of recipients to send the test combinations to, must be a value between 10 and 100." - }, - "subject_lines": { - "type": "array", - "title": "Subject Lines", - "description": "The possible subject lines to test. If no subject lines are provided, settings.subject_line will be used.", - "items": { - "type": "string" - } - }, - "send_times": { - "type": "array", - "title": "Send Times", - "description": "The possible send times to test. The times provided should be in the format YYYY-MM-DD HH:MM:SS. If send_times are provided to test, the test_size will be set to 100% and winner_criteria will be ignored.", - "items": { - "type": "string", - "format": "date-time" - } - }, - "from_names": { - "type": "array", - "title": "From Names", - "description": "The possible from names. The number of from_names provided must match the number of reply_to_addresses. If no from_names are provided, settings.from_name will be used.", - "items": { - "type": "string" - } - }, - "reply_to_addresses": { - "type": "array", - "title": "Reply To Addresses", - "description": "The possible reply-to addresses. The number of reply_to_addresses provided must match the number of from_names. If no reply_to_addresses are provided, settings.reply_to will be used.", - "items": { - "type": "string" - } - }, - "contents": { - "type": "array", - "title": "Content Descriptions", - "description": "Descriptions of possible email contents. To set campaign contents, make a PUT request to /campaigns/{campaign_id}/content with the field 'variate_contents'.", - "items": { - "type": "string" - }, - "readOnly": true - }, - "combinations": { - "type": "array", - "title": "Combinations", - "description": "Combinations of possible variables used to build emails.", - "readOnly": true, - "items": { - "type": "object", - "properties": { - "id": { - "type": "string", - "title": "ID", - "description": "Unique ID for the combination." - }, - "subject_line": { - "type": "integer", - "title": "Subject Line", - "description": "The index of `variate_settings.subject_lines` used." - }, - "send_time": { - "type": "integer", - "title": "Send Time", - "description": "The index of `variate_settings.send_times` used." - }, - "from_name": { - "type": "integer", - "title": "From Name", - "description": "The index of `variate_settings.from_names` used." - }, - "reply_to": { - "type": "integer", - "title": "Reply To", - "description": "The index of `variate_settings.reply_to_addresses` used." - }, - "content_description": { - "type": "integer", - "title": "Content Description", - "description": "The index of `variate_settings.contents` used." - }, - "recipients": { - "type": "integer", - "title": "Recipients", - "description": "The number of recipients for this combination." - } - } - } - } - } - }, - "tracking": { - "type": "object", - "title": "Campaign Tracking Options", - "description": "The tracking options for a campaign.", - "properties": { - "opens": { - "type": "boolean", - "title": "Opens", - "description": "Whether to [track opens](https://mailchimp.com/help/about-open-tracking/). Defaults to `true`. Cannot be set to false for variate campaigns." - }, - "html_clicks": { - "type": "boolean", - "title": "HTML Click Tracking", - "description": "Whether to [track clicks](https://mailchimp.com/help/enable-and-view-click-tracking/) in the HTML version of the campaign. Defaults to `true`. Cannot be set to false for variate campaigns." - }, - "text_clicks": { - "type": "boolean", - "title": "Plain-Text Click Tracking", - "description": "Whether to [track clicks](https://mailchimp.com/help/enable-and-view-click-tracking/) in the plain-text version of the campaign. Defaults to `true`. Cannot be set to false for variate campaigns." - }, - "goal_tracking": { - "type": "boolean", - "title": "Mailchimp Goal Tracking", - "description": "Whether to enable [Goal](https://mailchimp.com/help/about-connected-sites/) tracking." - }, - "ecomm360": { - "type": "boolean", - "title": "E-commerce Tracking", - "description": "Whether to enable [eCommerce360](https://mailchimp.com/help/connect-your-online-store-to-mailchimp/) tracking." - }, - "google_analytics": { - "type": "string", - "title": "Google Analytics Tracking", - "description": "The custom slug for [Google Analytics](https://mailchimp.com/help/integrate-google-analytics-with-mailchimp/) tracking (max of 50 bytes)." - }, - "clicktale": { - "type": "string", - "title": "ClickTale Analytics Tracking", - "description": "The custom slug for [ClickTale](https://mailchimp.com/help/additional-tracking-options-for-campaigns/) tracking (max of 50 bytes)." - }, - "salesforce": { - "type": "object", - "title": "Salesforce CRM Tracking", - "description": "Salesforce tracking options for a campaign. Must be using Mailchimp's built-in [Salesforce integration](https://mailchimp.com/help/integrate-salesforce-with-mailchimp/).", - "properties": { - "campaign": { - "type": "boolean", - "title": "Salesforce Campaign", - "description": "Create a campaign in a connected Salesforce account." - }, - "notes": { - "type": "boolean", - "title": "Salesforce Note", - "description": "Update contact notes for a campaign based on subscriber email addresses." - } - } - }, - "capsule": { - "type": "object", - "title": "Capsule CRM Tracking", - "description": "Capsule tracking options for a campaign. Must be using Mailchimp's built-in Capsule integration.", - "properties": { - "notes": { - "type": "boolean", - "title": "Capsule Note", - "description": "Update contact notes for a campaign based on subscriber email addresses." - } - } - } - } - }, - "rss_opts": { - "type": "object", - "title": "RSS Options", - "description": "[RSS](https://mailchimp.com/help/share-your-blog-posts-with-mailchimp/) options for a campaign.", - "properties": { - "feed_url": { - "type": "string", - "title": "Feed URL", - "format": "uri", - "description": "The URL for the RSS feed." - }, - "frequency": { - "type": "string", - "title": "Frequency", - "description": "The frequency of the RSS Campaign.", - "enum": ["daily", "weekly", "monthly"] - }, - "schedule": { - "type": "object", - "title": "Sending Schedule", - "description": "The schedule for sending the RSS Campaign.", - "properties": { - "hour": { - "type": "integer", - "minimum": 0, - "maximum": 23, - "title": "Sending Hour", - "description": "The hour to send the campaign in local time. Acceptable hours are 0-23. For example, '4' would be 4am in [your account's default time zone](https://mailchimp.com/help/set-account-defaults/)." - }, - "daily_send": { - "type": "object", - "title": "Daily Sending Days", - "description": "The days of the week to send a daily RSS Campaign.", - "properties": { - "sunday": { - "type": "boolean", - "title": "Sunday", - "description": "Sends the daily RSS Campaign on Sundays." - }, - "monday": { - "type": "boolean", - "title": "Monday", - "description": "Sends the daily RSS Campaign on Mondays." - }, - "tuesday": { - "type": "boolean", - "title": "tuesday", - "description": "Sends the daily RSS Campaign on Tuesdays." - }, - "wednesday": { - "type": "boolean", - "title": "Monday", - "description": "Sends the daily RSS Campaign on Wednesdays." - }, - "thursday": { - "type": "boolean", - "title": "Thursday", - "description": "Sends the daily RSS Campaign on Thursdays." - }, - "friday": { - "type": "boolean", - "title": "Friday", - "description": "Sends the daily RSS Campaign on Fridays." - }, - "saturday": { - "type": "boolean", - "title": "Saturday", - "description": "Sends the daily RSS Campaign on Saturdays." - } - } - }, - "weekly_send_day": { - "type": "string", - "enum": [ - "sunday", - "monday", - "tuesday", - "wednesday", - "thursday", - "friday", - "saturday" - ], - "title": "Weekly Sending Day", - "description": "The day of the week to send a weekly RSS Campaign." - }, - "monthly_send_date": { - "type": "number", - "minimum": 0, - "maximum": 31, - "title": "Monthly Sending Day", - "description": "The day of the month to send a monthly RSS Campaign. Acceptable days are 0-31, where '0' is always the last day of a month. Months with fewer than the selected number of days will not have an RSS campaign sent out that day. For example, RSS Campaigns set to send on the 30th will not go out in February." - } - } - }, - "last_sent": { - "type": "string", - "format": "date-time", - "title": "Last Sent", - "description": "The date the campaign was last sent.", - "readOnly": true - }, - "constrain_rss_img": { - "type": "boolean", - "title": "Constrain RSS Images", - "description": "Whether to add CSS to images in the RSS feed to constrain their width in campaigns." - } - } - }, - "ab_split_opts": { - "type": "object", - "title": "A/B Testing Options", - "description": "[A/B Testing](https://mailchimp.com/help/about-ab-testing-campaigns/) options for a campaign.", - "readOnly": true, - "properties": { - "split_test": { - "type": "string", - "title": "Split Test", - "description": "The type of AB split to run.", - "enum": ["subject", "from_name", "schedule"] - }, - "pick_winner": { - "type": "string", - "title": "Pick Winner", - "description": "How we should evaluate a winner. Based on 'opens', 'clicks', or 'manual'.", - "enum": ["opens", "clicks", "manual"] - }, - "wait_units": { - "type": "string", - "title": "Wait Time", - "description": "How unit of time for measuring the winner ('hours' or 'days'). This cannot be changed after a campaign is sent.", - "enum": ["hours", "days"] - }, - "wait_time": { - "type": "integer", - "title": "Wait Time", - "description": "The amount of time to wait before picking a winner. This cannot be changed after a campaign is sent." - }, - "split_size": { - "type": "integer", - "minimum": 1, - "maximum": 50, - "title": "Split Size", - "description": "The size of the split groups. Campaigns split based on 'schedule' are forced to have a 50/50 split. Valid split integers are between 1-50." - }, - "from_name_a": { - "type": "string", - "title": "From Name Group A", - "description": "For campaigns split on 'From Name', the name for Group A." - }, - "from_name_b": { - "type": "string", - "title": "From Name Group B", - "description": "For campaigns split on 'From Name', the name for Group B." - }, - "reply_email_a": { - "type": "string", - "title": "Reply Email Group A", - "description": "For campaigns split on 'From Name', the reply-to address for Group A." - }, - "reply_email_b": { - "type": "string", - "title": "Reply Email Group B", - "description": "For campaigns split on 'From Name', the reply-to address for Group B." - }, - "subject_a": { - "type": "string", - "title": "Subject Line Group A", - "description": "For campaigns split on 'Subject Line', the subject line for Group A." - }, - "subject_b": { - "type": "string", - "title": "Subject Line Group B", - "description": "For campaigns split on 'Subject Line', the subject line for Group B." - }, - "send_time_a": { - "type": "string", - "format": "date-time", - "title": "Send Time Group A", - "description": "The send time for Group A." - }, - "send_time_b": { - "type": "string", - "format": "date-time", - "title": "Send Time Group B", - "description": "The send time for Group B." - }, - "send_time_winner": { - "type": "string", - "title": "Send Time Winner", - "description": "The send time for the winning version." - } - } - }, - "social_card": { - "type": "object", - "title": "Campaign Social Card", - "description": "The preview for the campaign, rendered by social networks like Facebook and Twitter. [Learn more](https://mailchimp.com/help/enable-and-customize-social-cards/).", - "properties": { - "image_url": { - "type": "string", - "title": "Image URL", - "description": "The url for the header image for the card." - }, - "description": { - "type": "string", - "title": "Campaign Description", - "description": "A short summary of the campaign to display." - }, - "title": { - "type": "string", - "title": "Title", - "description": "The title for the card. Typically the subject line of the campaign." - } - } - }, - "report_summary": { - "type": "object", - "title": "Campaign Report Summary", - "description": "For sent campaigns, a summary of opens, clicks, and e-commerce data.", - "properties": { - "opens": { - "type": "integer", - "title": "Automation Opens", - "description": "The total number of opens for a campaign.", - "readOnly": true - }, - "unique_opens": { - "type": "integer", - "title": "Unique Opens", - "description": "The number of unique opens.", - "readOnly": true - }, - "open_rate": { - "type": "number", - "title": "Open Rate", - "description": "The number of unique opens divided by the total number of successful deliveries.", - "readOnly": true - }, - "clicks": { - "type": "integer", - "title": "Total Clicks", - "description": "The total number of clicks for an campaign.", - "readOnly": true - }, - "subscriber_clicks": { - "type": "integer", - "title": "Unique Subscriber Clicks", - "description": "The number of unique clicks.", - "readOnly": true - }, - "click_rate": { - "type": "number", - "title": "Click Rate", - "description": "The number of unique clicks divided by the total number of successful deliveries.", - "readOnly": true - }, - "ecommerce": { - "type": "object", - "title": "E-Commerce Report", - "description": "E-Commerce stats for a campaign.", - "properties": { - "total_orders": { - "type": "integer", - "title": "Total Orders", - "description": "The total orders for a campaign.", - "readOnly": true - }, - "total_spent": { - "type": "number", - "title": "Total Spent", - "description": "The total spent for a campaign. Calculated as the sum of all order totals with no deductions.", - "readOnly": true - }, - "total_revenue": { - "type": "number", - "title": "Total Revenue", - "description": "The total revenue for a campaign. Calculated as the sum of all order totals minus shipping and tax totals.", - "readOnly": true - } - } - } - } - }, - "delivery_status": { - "type": "object", - "title": "Campaign Delivery Status", - "description": "Updates on campaigns in the process of sending.", - "properties": { - "enabled": { - "type": "boolean", - "title": "Delivery Status Enabled", - "description": "Whether Campaign Delivery Status is enabled for this account and campaign.", - "readOnly": true - }, - "can_cancel": { - "type": "boolean", - "title": "Campaign Cancelable", - "description": "Whether a campaign send can be canceled.", - "readOnly": true - }, - "status": { - "type": "string", - "title": "Campaign Delivery Status", - "description": "The current state of a campaign delivery.", - "enum": ["delivering", "delivered", "canceling", "canceled"], - "readOnly": true - }, - "emails_sent": { - "type": "integer", - "title": "Emails Sent", - "description": "The total number of emails confirmed sent for this campaign so far.", - "readOnly": true - }, - "emails_canceled": { - "type": "integer", - "title": "Emails Canceled", - "description": "The total number of emails canceled for this campaign.", - "readOnly": true - } - } - }, - "_links": { - "title": "Links", - "description": "A list of link types and descriptions for the API schema documents.", - "type": "array", - "items": { - "type": "object", - "title": "Resource Link", - "description": "This object represents a link from the resource where it is found to another resource or action that may be performed.", - "properties": { - "rel": { - "type": "string", - "title": "Rel", - "description": "As with an HTML 'rel' attribute, this describes the type of link.", - "readOnly": true - }, - "href": { - "type": "string", - "title": "Href", - "description": "This property contains a fully-qualified URL that can be called to retrieve the linked resource or perform the linked action.", - "readOnly": true - }, - "method": { - "type": "string", - "title": "Method", - "description": "The HTTP method that should be used when accessing the URL defined in 'href'.", - "enum": [ - "GET", - "POST", - "PUT", - "PATCH", - "DELETE", - "OPTIONS", - "HEAD" - ], - "readOnly": true - }, - "targetSchema": { - "type": "string", - "title": "Target Schema", - "description": "For GETs, this is a URL representing the schema that the response should conform to.", - "readOnly": true - }, - "schema": { - "type": "string", - "title": "Schema", - "description": "For HTTP methods that can receive bodies (POST and PUT), this is a URL representing the schema that the body should conform to.", - "readOnly": true - } - } - }, - "readOnly": true - } - } - } -} diff --git a/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/schemas/Lists.json b/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/schemas/Lists.json deleted file mode 100644 index aeda1dd2b8fa6..0000000000000 --- a/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/schemas/Lists.json +++ /dev/null @@ -1,362 +0,0 @@ -{ - "name": "Lists", - "json_schema": { - "type": "object", - "title": "Subscriber List", - "description": "Information about a specific list.", - "properties": { - "id": { - "type": "string", - "title": "List ID", - "description": "A string that uniquely identifies this list.", - "readOnly": true - }, - "web_id": { - "type": "integer", - "title": "List Web ID", - "description": "The ID used in the Mailchimp web application. View this list in your Mailchimp account at `https://{dc}.admin.mailchimp.com/lists/members/?id={web_id}`.", - "readOnly": true - }, - "name": { - "type": "string", - "title": "List Name", - "description": "The name of the list." - }, - "contact": { - "type": "object", - "title": "List Contact", - "description": "[Contact information displayed in campaign footers](https://mailchimp.com/help/about-campaign-footers/) to comply with international spam laws.", - "properties": { - "company": { - "type": "string", - "title": "Company Name", - "description": "The company name for the list." - }, - "address1": { - "type": "string", - "title": "Address", - "description": "The street address for the list contact." - }, - "address2": { - "type": "string", - "title": "Address", - "description": "The street address for the list contact." - }, - "city": { - "type": "string", - "title": "City", - "description": "The city for the list contact." - }, - "state": { - "type": "string", - "title": "State", - "description": "The state for the list contact." - }, - "zip": { - "type": "string", - "title": "Postal Code", - "description": "The postal or zip code for the list contact." - }, - "country": { - "type": "string", - "title": "Country Code", - "description": "A two-character ISO3166 country code. Defaults to US if invalid." - }, - "phone": { - "type": "string", - "title": "Phone Number", - "description": "The phone number for the list contact." - } - } - }, - "permission_reminder": { - "type": "string", - "title": "Permission Reminder", - "description": "The [permission reminder](https://mailchimp.com/help/edit-the-permission-reminder/) for the list." - }, - "use_archive_bar": { - "type": "boolean", - "title": "Use Archive Bar", - "description": "Whether campaigns for this list use the [Archive Bar](https://mailchimp.com/help/about-email-campaign-archives-and-pages/) in archives by default.", - "default": false - }, - "campaign_defaults": { - "type": "object", - "title": "Campaign Defaults", - "description": "[Default values for campaigns](https://mailchimp.com/help/edit-your-emails-subject-preview-text-from-name-or-from-email-address/) created for this list.", - "properties": { - "from_name": { - "type": "string", - "title": "Sender's Name", - "description": "The default from name for campaigns sent to this list." - }, - "from_email": { - "type": "string", - "title": "Sender's Email Address", - "description": "The default from email for campaigns sent to this list." - }, - "subject": { - "type": "string", - "title": "Subject", - "description": "The default subject line for campaigns sent to this list." - }, - "language": { - "type": "string", - "title": "Language", - "description": "The default language for this lists's forms." - } - } - }, - "notify_on_subscribe": { - "type": "string", - "title": "Notify on Subscribe", - "description": "The email address to send [subscribe notifications](https://mailchimp.com/help/change-subscribe-and-unsubscribe-notifications/) to.", - "default": false - }, - "notify_on_unsubscribe": { - "type": "string", - "title": "Notify on Unsubscribe", - "description": "The email address to send [unsubscribe notifications](https://mailchimp.com/help/change-subscribe-and-unsubscribe-notifications/) to.", - "default": false - }, - "date_created": { - "type": "string", - "title": "Creation Date", - "description": "The date and time that this list was created in ISO 8601 format.", - "format": "date-time", - "readOnly": true - }, - "list_rating": { - "type": "integer", - "title": "List Rating", - "description": "An auto-generated activity score for the list (0-5).", - "readOnly": true - }, - "email_type_option": { - "type": "boolean", - "title": "Email Type Option", - "description": "Whether the list supports [multiple formats for emails](https://mailchimp.com/help/change-list-name-and-defaults/). When set to `true`, subscribers can choose whether they want to receive HTML or plain-text emails. When set to `false`, subscribers will receive HTML emails, with a plain-text alternative backup." - }, - "subscribe_url_short": { - "type": "string", - "title": "Subscribe URL Short", - "description": "Our [EepURL shortened](https://mailchimp.com/help/share-your-signup-form/) version of this list's subscribe form.", - "readOnly": true - }, - "subscribe_url_long": { - "type": "string", - "title": "Subscribe URL Long", - "description": "The full version of this list's subscribe form (host will vary).", - "readOnly": true - }, - "beamer_address": { - "type": "string", - "title": "Beamer Address", - "description": "The list's [Email Beamer](https://mailchimp.com/help/use-email-beamer-to-create-a-campaign/) address.", - "readOnly": true - }, - "visibility": { - "type": "string", - "title": "Visibility", - "enum": ["pub", "prv"], - "description": "Whether this list is [public or private](https://mailchimp.com/help/about-list-publicity/)." - }, - "double_optin": { - "type": "boolean", - "title": "Double Opt In", - "description": "Whether or not to require the subscriber to confirm subscription via email.", - "default": false - }, - "has_welcome": { - "type": "boolean", - "title": "Has Welcome", - "description": "Whether or not this list has a welcome automation connected. Welcome Automations: welcomeSeries, singleWelcome, emailFollowup.", - "default": false, - "example": false - }, - "marketing_permissions": { - "type": "boolean", - "title": "Marketing Permissions", - "description": "Whether or not the list has marketing permissions (eg. GDPR) enabled.", - "default": false - }, - "modules": { - "type": "array", - "title": "Modules", - "description": "Any list-specific modules installed for this list.", - "items": { - "type": "string" - }, - "readOnly": true - }, - "stats": { - "type": "object", - "title": "Statistics", - "description": "Stats for the list. Many of these are cached for at least five minutes.", - "readOnly": true, - "properties": { - "member_count": { - "type": "integer", - "title": "Member Count", - "description": "The number of active members in the list.", - "readOnly": true - }, - "total_contacts": { - "type": "integer", - "title": "Total Contacts", - "description": "The number of contacts in the list, including subscribed, unsubscribed, pending, cleaned, deleted, transactional, and those that need to be reconfirmed.", - "readOnly": true - }, - "unsubscribe_count": { - "type": "integer", - "title": "Unsubscribe Count", - "description": "The number of members who have unsubscribed from the list.", - "readOnly": true - }, - "cleaned_count": { - "type": "integer", - "title": "Cleaned Count", - "description": "The number of members cleaned from the list.", - "readOnly": true - }, - "member_count_since_send": { - "type": "integer", - "title": "Member Count Since Send", - "description": "The number of active members in the list since the last campaign was sent.", - "readOnly": true - }, - "unsubscribe_count_since_send": { - "type": "integer", - "title": "Unsubscribe Count Since Send", - "description": "The number of members who have unsubscribed since the last campaign was sent.", - "readOnly": true - }, - "cleaned_count_since_send": { - "type": "integer", - "title": "Cleaned Count Since Send", - "description": "The number of members cleaned from the list since the last campaign was sent.", - "readOnly": true - }, - "campaign_count": { - "type": "integer", - "title": "Campaign Count", - "description": "The number of campaigns in any status that use this list.", - "readOnly": true - }, - "campaign_last_sent": { - "type": "string", - "format": "date-time", - "title": "Campaign Last Sent", - "description": "The date and time the last campaign was sent to this list in ISO 8601 format. This is updated when a campaign is sent to 10 or more recipients.", - "readOnly": true - }, - "merge_field_count": { - "type": "integer", - "title": "Merge Var Count", - "description": "The number of merge vars for this list (not EMAIL, which is required).", - "readOnly": true - }, - "avg_sub_rate": { - "type": "number", - "title": "Average Subscription Rate", - "description": "The average number of subscriptions per month for the list (not returned if we haven't calculated it yet).", - "readOnly": true - }, - "avg_unsub_rate": { - "type": "number", - "title": "Average Unsubscription Rate", - "description": "The average number of unsubscriptions per month for the list (not returned if we haven't calculated it yet).", - "readOnly": true - }, - "target_sub_rate": { - "type": "number", - "title": "Average Subscription Rate", - "description": "The target number of subscriptions per month for the list to keep it growing (not returned if we haven't calculated it yet).", - "readOnly": true - }, - "open_rate": { - "type": "number", - "title": "Open Rate", - "description": "The average open rate (a percentage represented as a number between 0 and 100) per campaign for the list (not returned if we haven't calculated it yet).", - "readOnly": true - }, - "click_rate": { - "type": "number", - "title": "Click Rate", - "description": "The average click rate (a percentage represented as a number between 0 and 100) per campaign for the list (not returned if we haven't calculated it yet).", - "readOnly": true - }, - "last_sub_date": { - "type": "string", - "format": "date-time", - "title": "Date of Last List Subscribe", - "description": "The date and time of the last time someone subscribed to this list in ISO 8601 format.", - "readOnly": true - }, - "last_unsub_date": { - "type": "string", - "format": "date-time", - "title": "Date of Last List Unsubscribe", - "description": "The date and time of the last time someone unsubscribed from this list in ISO 8601 format.", - "readOnly": true - } - } - }, - "_links": { - "title": "Links", - "description": "A list of link types and descriptions for the API schema documents.", - "type": "array", - "items": { - "type": "object", - "title": "Resource Link", - "description": "This object represents a link from the resource where it is found to another resource or action that may be performed.", - "properties": { - "rel": { - "type": "string", - "title": "Rel", - "description": "As with an HTML 'rel' attribute, this describes the type of link.", - "readOnly": true - }, - "href": { - "type": "string", - "title": "Href", - "description": "This property contains a fully-qualified URL that can be called to retrieve the linked resource or perform the linked action.", - "readOnly": true - }, - "method": { - "type": "string", - "title": "Method", - "description": "The HTTP method that should be used when accessing the URL defined in 'href'.", - "enum": [ - "GET", - "POST", - "PUT", - "PATCH", - "DELETE", - "OPTIONS", - "HEAD" - ], - "readOnly": true - }, - "targetSchema": { - "type": "string", - "title": "Target Schema", - "description": "For GETs, this is a URL representing the schema that the response should conform to.", - "readOnly": true - }, - "schema": { - "type": "string", - "title": "Schema", - "description": "For HTTP methods that can receive bodies (POST and PUT), this is a URL representing the schema that the body should conform to.", - "readOnly": true - } - } - }, - "readOnly": true - } - } - }, - "supported_sync_modes": ["full_refresh", "incremental"], - "source_defined_cursor": true, - "default_cursor_field": ["date_created"] -} diff --git a/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/schemas/campaigns.json b/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/schemas/campaigns.json new file mode 100644 index 0000000000000..1e910e1ee9a3b --- /dev/null +++ b/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/schemas/campaigns.json @@ -0,0 +1,733 @@ +{ + "type": "object", + "title": "Campaign", + "description": "A summary of an individual campaign's settings and content.", + "properties": { + "id": { + "type": "string", + "title": "Campaign ID", + "description": "A string that uniquely identifies this campaign.", + "readOnly": true + }, + "web_id": { + "type": "integer", + "title": "Campaign Web ID", + "description": "The ID used in the Mailchimp web application. View this campaign in your Mailchimp account at `https://{dc}.admin.mailchimp.com/campaigns/show/?id={web_id}`.", + "readOnly": true + }, + "parent_campaign_id": { + "type": "string", + "title": "Parent Campaign ID", + "description": "If this campaign is the child of another campaign, this identifies the parent campaign. For Example, for RSS or Automation children.", + "readOnly": true + }, + "type": { + "$ref": "https://us1.api.mailchimp.com/schema/3.0/Definitions/CampaignType.json" + }, + "create_time": { + "type": "string", + "format": "date-time", + "title": "Create Time", + "description": "The date and time the campaign was created in ISO 8601 format.", + "readOnly": true + }, + "archive_url": { + "type": "string", + "title": "Archive URL", + "description": "The link to the campaign's archive version in ISO 8601 format.", + "readOnly": true + }, + "long_archive_url": { + "type": "string", + "title": "Long Archive URL", + "description": "The original link to the campaign's archive version.", + "readOnly": true + }, + "status": { + "$ref": "https://us1.api.mailchimp.com/schema/3.0/Definitions/CampaignStatus.json" + }, + "emails_sent": { + "type": "integer", + "title": "Emails Sent", + "description": "The total number of emails sent for this campaign.", + "readOnly": true + }, + "send_time": { + "type": "string", + "format": "date-time", + "title": "Send Time", + "description": "The date and time a campaign was sent.", + "readOnly": true + }, + "content_type": { + "type": "string", + "title": "Content Type", + "description": "How the campaign's content is put together.", + "enum": ["template", "html", "url", "multichannel"] + }, + "needs_block_refresh": { + "type": "boolean", + "title": "Needs Block Refresh", + "description": "Determines if the campaign needs its blocks refreshed by opening the web-based campaign editor. Deprecated and will always return false.", + "readOnly": true + }, + "resendable": { + "type": "boolean", + "title": "Resendable", + "description": "Determines if the campaign qualifies to be resent to non-openers.", + "readOnly": true + }, + "recipients": { + "type": "object", + "title": "List", + "description": "List settings for the campaign.", + "properties": { + "list_id": { + "type": "string", + "title": "List ID", + "description": "The unique list id." + }, + "list_is_active": { + "type": "boolean", + "title": "List Status", + "description": "The status of the list used, namely if it's deleted or disabled.", + "readOnly": true + }, + "list_name": { + "type": "string", + "title": "List Name", + "description": "The name of the list.", + "readOnly": true + }, + "segment_text": { + "type": "string", + "title": "Segment Text", + "description": "A description of the [segment](https://mailchimp.com/help/create-and-send-to-a-segment/) used for the campaign. Formatted as a string marked up with HTML.", + "readOnly": true + }, + "recipient_count": { + "type": "integer", + "title": "Recipient Count", + "description": "Count of the recipients on the associated list. Formatted as an integer.", + "readOnly": true + }, + "segment_opts": { + "$ref": "https://us1.api.mailchimp.com/schema/3.0/Definitions/SegmentationOptions.json" + } + } + }, + "settings": { + "type": "object", + "title": "Campaign Settings", + "description": "The settings for your campaign, including subject, from name, reply-to address, and more.", + "properties": { + "subject_line": { + "type": "string", + "title": "Campaign Subject Line", + "description": "The subject line for the campaign." + }, + "preview_text": { + "type": "string", + "title": "Campaign Preview Text", + "description": "The preview text for the campaign." + }, + "title": { + "type": "string", + "title": "Campaign Title", + "description": "The title of the campaign." + }, + "from_name": { + "type": "string", + "title": "From Name", + "description": "The 'from' name on the campaign (not an email address)." + }, + "reply_to": { + "type": "string", + "title": "Reply To Address", + "description": "The reply-to email address for the campaign." + }, + "use_conversation": { + "type": "boolean", + "title": "Conversation", + "description": "Use Mailchimp Conversation feature to manage out-of-office replies." + }, + "to_name": { + "type": "string", + "title": "To Name", + "description": "The campaign's custom 'To' name. Typically the first name [merge field](https://mailchimp.com/help/getting-started-with-merge-tags/)." + }, + "folder_id": { + "type": "string", + "title": "Folder ID", + "description": "If the campaign is listed in a folder, the id for that folder." + }, + "authenticate": { + "type": "boolean", + "title": "Authentication", + "description": "Whether Mailchimp [authenticated](https://mailchimp.com/help/about-email-authentication/) the campaign. Defaults to `true`." + }, + "auto_footer": { + "type": "boolean", + "title": "Auto-Footer", + "description": "Automatically append Mailchimp's [default footer](https://mailchimp.com/help/about-campaign-footers/) to the campaign." + }, + "inline_css": { + "type": "boolean", + "title": "Inline CSS", + "description": "Automatically inline the CSS included with the campaign content." + }, + "auto_tweet": { + "type": "boolean", + "title": "Auto-Tweet", + "description": "Automatically tweet a link to the [campaign archive](https://mailchimp.com/help/about-email-campaign-archives-and-pages/) page when the campaign is sent." + }, + "auto_fb_post": { + "type": "array", + "title": "Auto Post to Facebook", + "description": "An array of [Facebook](https://mailchimp.com/help/connect-or-disconnect-the-facebook-integration/) page ids to auto-post to.", + "items": { + "type": "string" + } + }, + "fb_comments": { + "type": "boolean", + "title": "Facebook Comments", + "description": "Allows Facebook comments on the campaign (also force-enables the Campaign Archive toolbar). Defaults to `true`." + }, + "timewarp": { + "type": "boolean", + "title": "Timewarp Send", + "description": "Send this campaign using [Timewarp](https://mailchimp.com/help/use-timewarp/).", + "readOnly": true + }, + "template_id": { + "type": "integer", + "title": "Template ID", + "description": "The id for the template used in this campaign.", + "readOnly": false + }, + "drag_and_drop": { + "type": "boolean", + "title": "Drag And Drop Campaign", + "description": "Whether the campaign uses the drag-and-drop editor.", + "readOnly": true + } + } + }, + "variate_settings": { + "type": "object", + "title": "A/B Test Options", + "description": "The settings specific to A/B test campaigns.", + "properties": { + "winning_combination_id": { + "type": "string", + "title": "Winning Combination ID", + "description": "ID for the winning combination.", + "readOnly": true + }, + "winning_campaign_id": { + "type": "string", + "title": "Winning Campaign ID", + "description": "ID of the campaign that was sent to the remaining recipients based on the winning combination.", + "readOnly": true + }, + "winner_criteria": { + "type": "string", + "title": "Winning Criteria", + "description": "The combination that performs the best. This may be determined automatically by click rate, open rate, or total revenue -- or you may choose manually based on the reporting data you find the most valuable. For Multivariate Campaigns testing send_time, winner_criteria is ignored. For Multivariate Campaigns with 'manual' as the winner_criteria, the winner must be chosen in the Mailchimp web application.", + "enum": ["opens", "clicks", "manual", "total_revenue"] + }, + "wait_time": { + "type": "integer", + "title": "Wait Time", + "description": "The number of minutes to wait before choosing the winning campaign. The value of wait_time must be greater than 0 and in whole hours, specified in minutes." + }, + "test_size": { + "type": "integer", + "title": "Test Size", + "description": "The percentage of recipients to send the test combinations to, must be a value between 10 and 100." + }, + "subject_lines": { + "type": "array", + "title": "Subject Lines", + "description": "The possible subject lines to test. If no subject lines are provided, settings.subject_line will be used.", + "items": { + "type": "string" + } + }, + "send_times": { + "type": "array", + "title": "Send Times", + "description": "The possible send times to test. The times provided should be in the format YYYY-MM-DD HH:MM:SS. If send_times are provided to test, the test_size will be set to 100% and winner_criteria will be ignored.", + "items": { + "type": "string", + "format": "date-time" + } + }, + "from_names": { + "type": "array", + "title": "From Names", + "description": "The possible from names. The number of from_names provided must match the number of reply_to_addresses. If no from_names are provided, settings.from_name will be used.", + "items": { + "type": "string" + } + }, + "reply_to_addresses": { + "type": "array", + "title": "Reply To Addresses", + "description": "The possible reply-to addresses. The number of reply_to_addresses provided must match the number of from_names. If no reply_to_addresses are provided, settings.reply_to will be used.", + "items": { + "type": "string" + } + }, + "contents": { + "type": "array", + "title": "Content Descriptions", + "description": "Descriptions of possible email contents. To set campaign contents, make a PUT request to /campaigns/{campaign_id}/content with the field 'variate_contents'.", + "items": { + "type": "string" + }, + "readOnly": true + }, + "combinations": { + "type": "array", + "title": "Combinations", + "description": "Combinations of possible variables used to build emails.", + "readOnly": true, + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "title": "ID", + "description": "Unique ID for the combination." + }, + "subject_line": { + "type": "integer", + "title": "Subject Line", + "description": "The index of `variate_settings.subject_lines` used." + }, + "send_time": { + "type": "integer", + "title": "Send Time", + "description": "The index of `variate_settings.send_times` used." + }, + "from_name": { + "type": "integer", + "title": "From Name", + "description": "The index of `variate_settings.from_names` used." + }, + "reply_to": { + "type": "integer", + "title": "Reply To", + "description": "The index of `variate_settings.reply_to_addresses` used." + }, + "content_description": { + "type": "integer", + "title": "Content Description", + "description": "The index of `variate_settings.contents` used." + }, + "recipients": { + "type": "integer", + "title": "Recipients", + "description": "The number of recipients for this combination." + } + } + } + } + } + }, + "tracking": { + "type": "object", + "title": "Campaign Tracking Options", + "description": "The tracking options for a campaign.", + "properties": { + "opens": { + "type": "boolean", + "title": "Opens", + "description": "Whether to [track opens](https://mailchimp.com/help/about-open-tracking/). Defaults to `true`. Cannot be set to false for variate campaigns." + }, + "html_clicks": { + "type": "boolean", + "title": "HTML Click Tracking", + "description": "Whether to [track clicks](https://mailchimp.com/help/enable-and-view-click-tracking/) in the HTML version of the campaign. Defaults to `true`. Cannot be set to false for variate campaigns." + }, + "text_clicks": { + "type": "boolean", + "title": "Plain-Text Click Tracking", + "description": "Whether to [track clicks](https://mailchimp.com/help/enable-and-view-click-tracking/) in the plain-text version of the campaign. Defaults to `true`. Cannot be set to false for variate campaigns." + }, + "goal_tracking": { + "type": "boolean", + "title": "Mailchimp Goal Tracking", + "description": "Whether to enable [Goal](https://mailchimp.com/help/about-connected-sites/) tracking." + }, + "ecomm360": { + "type": "boolean", + "title": "E-commerce Tracking", + "description": "Whether to enable [eCommerce360](https://mailchimp.com/help/connect-your-online-store-to-mailchimp/) tracking." + }, + "google_analytics": { + "type": "string", + "title": "Google Analytics Tracking", + "description": "The custom slug for [Google Analytics](https://mailchimp.com/help/integrate-google-analytics-with-mailchimp/) tracking (max of 50 bytes)." + }, + "clicktale": { + "type": "string", + "title": "ClickTale Analytics Tracking", + "description": "The custom slug for [ClickTale](https://mailchimp.com/help/additional-tracking-options-for-campaigns/) tracking (max of 50 bytes)." + }, + "salesforce": { + "type": "object", + "title": "Salesforce CRM Tracking", + "description": "Salesforce tracking options for a campaign. Must be using Mailchimp's built-in [Salesforce integration](https://mailchimp.com/help/integrate-salesforce-with-mailchimp/).", + "properties": { + "campaign": { + "type": "boolean", + "title": "Salesforce Campaign", + "description": "Create a campaign in a connected Salesforce account." + }, + "notes": { + "type": "boolean", + "title": "Salesforce Note", + "description": "Update contact notes for a campaign based on subscriber email addresses." + } + } + }, + "capsule": { + "type": "object", + "title": "Capsule CRM Tracking", + "description": "Capsule tracking options for a campaign. Must be using Mailchimp's built-in Capsule integration.", + "properties": { + "notes": { + "type": "boolean", + "title": "Capsule Note", + "description": "Update contact notes for a campaign based on subscriber email addresses." + } + } + } + } + }, + "rss_opts": { + "type": "object", + "title": "RSS Options", + "description": "[RSS](https://mailchimp.com/help/share-your-blog-posts-with-mailchimp/) options for a campaign.", + "properties": { + "feed_url": { + "type": "string", + "title": "Feed URL", + "format": "uri", + "description": "The URL for the RSS feed." + }, + "frequency": { + "type": "string", + "title": "Frequency", + "description": "The frequency of the RSS Campaign.", + "enum": ["daily", "weekly", "monthly"] + }, + "schedule": { + "type": "object", + "title": "Sending Schedule", + "description": "The schedule for sending the RSS Campaign.", + "properties": { + "hour": { + "type": "integer", + "minimum": 0, + "maximum": 23, + "title": "Sending Hour", + "description": "The hour to send the campaign in local time. Acceptable hours are 0-23. For example, '4' would be 4am in [your account's default time zone](https://mailchimp.com/help/set-account-defaults/)." + }, + "daily_send": { + "type": "object", + "title": "Daily Sending Days", + "description": "The days of the week to send a daily RSS Campaign.", + "properties": { + "sunday": { + "type": "boolean", + "title": "Sunday", + "description": "Sends the daily RSS Campaign on Sundays." + }, + "monday": { + "type": "boolean", + "title": "Monday", + "description": "Sends the daily RSS Campaign on Mondays." + }, + "tuesday": { + "type": "boolean", + "title": "tuesday", + "description": "Sends the daily RSS Campaign on Tuesdays." + }, + "wednesday": { + "type": "boolean", + "title": "Monday", + "description": "Sends the daily RSS Campaign on Wednesdays." + }, + "thursday": { + "type": "boolean", + "title": "Thursday", + "description": "Sends the daily RSS Campaign on Thursdays." + }, + "friday": { + "type": "boolean", + "title": "Friday", + "description": "Sends the daily RSS Campaign on Fridays." + }, + "saturday": { + "type": "boolean", + "title": "Saturday", + "description": "Sends the daily RSS Campaign on Saturdays." + } + } + }, + "weekly_send_day": { + "type": "string", + "enum": [ + "sunday", + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday" + ], + "title": "Weekly Sending Day", + "description": "The day of the week to send a weekly RSS Campaign." + }, + "monthly_send_date": { + "type": "number", + "minimum": 0, + "maximum": 31, + "title": "Monthly Sending Day", + "description": "The day of the month to send a monthly RSS Campaign. Acceptable days are 0-31, where '0' is always the last day of a month. Months with fewer than the selected number of days will not have an RSS campaign sent out that day. For example, RSS Campaigns set to send on the 30th will not go out in February." + } + } + }, + "last_sent": { + "type": "string", + "format": "date-time", + "title": "Last Sent", + "description": "The date the campaign was last sent.", + "readOnly": true + }, + "constrain_rss_img": { + "type": "boolean", + "title": "Constrain RSS Images", + "description": "Whether to add CSS to images in the RSS feed to constrain their width in campaigns." + } + } + }, + "ab_split_opts": { + "type": "object", + "title": "A/B Testing Options", + "description": "[A/B Testing](https://mailchimp.com/help/about-ab-testing-campaigns/) options for a campaign.", + "readOnly": true, + "properties": { + "split_test": { + "type": "string", + "title": "Split Test", + "description": "The type of AB split to run.", + "enum": ["subject", "from_name", "schedule"] + }, + "pick_winner": { + "type": "string", + "title": "Pick Winner", + "description": "How we should evaluate a winner. Based on 'opens', 'clicks', or 'manual'.", + "enum": ["opens", "clicks", "manual"] + }, + "wait_units": { + "type": "string", + "title": "Wait Time", + "description": "How unit of time for measuring the winner ('hours' or 'days'). This cannot be changed after a campaign is sent.", + "enum": ["hours", "days"] + }, + "wait_time": { + "type": "integer", + "title": "Wait Time", + "description": "The amount of time to wait before picking a winner. This cannot be changed after a campaign is sent." + }, + "split_size": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "title": "Split Size", + "description": "The size of the split groups. Campaigns split based on 'schedule' are forced to have a 50/50 split. Valid split integers are between 1-50." + }, + "from_name_a": { + "type": "string", + "title": "From Name Group A", + "description": "For campaigns split on 'From Name', the name for Group A." + }, + "from_name_b": { + "type": "string", + "title": "From Name Group B", + "description": "For campaigns split on 'From Name', the name for Group B." + }, + "reply_email_a": { + "type": "string", + "title": "Reply Email Group A", + "description": "For campaigns split on 'From Name', the reply-to address for Group A." + }, + "reply_email_b": { + "type": "string", + "title": "Reply Email Group B", + "description": "For campaigns split on 'From Name', the reply-to address for Group B." + }, + "subject_a": { + "type": "string", + "title": "Subject Line Group A", + "description": "For campaigns split on 'Subject Line', the subject line for Group A." + }, + "subject_b": { + "type": "string", + "title": "Subject Line Group B", + "description": "For campaigns split on 'Subject Line', the subject line for Group B." + }, + "send_time_a": { + "type": "string", + "format": "date-time", + "title": "Send Time Group A", + "description": "The send time for Group A." + }, + "send_time_b": { + "type": "string", + "format": "date-time", + "title": "Send Time Group B", + "description": "The send time for Group B." + }, + "send_time_winner": { + "type": "string", + "title": "Send Time Winner", + "description": "The send time for the winning version." + } + } + }, + "social_card": { + "type": "object", + "title": "Campaign Social Card", + "description": "The preview for the campaign, rendered by social networks like Facebook and Twitter. [Learn more](https://mailchimp.com/help/enable-and-customize-social-cards/).", + "properties": { + "image_url": { + "type": "string", + "title": "Image URL", + "description": "The url for the header image for the card." + }, + "description": { + "type": "string", + "title": "Campaign Description", + "description": "A short summary of the campaign to display." + }, + "title": { + "type": "string", + "title": "Title", + "description": "The title for the card. Typically the subject line of the campaign." + } + } + }, + "report_summary": { + "type": "object", + "title": "Campaign Report Summary", + "description": "For sent campaigns, a summary of opens, clicks, and e-commerce data.", + "properties": { + "opens": { + "type": "integer", + "title": "Automation Opens", + "description": "The total number of opens for a campaign.", + "readOnly": true + }, + "unique_opens": { + "type": "integer", + "title": "Unique Opens", + "description": "The number of unique opens.", + "readOnly": true + }, + "open_rate": { + "type": "number", + "title": "Open Rate", + "description": "The number of unique opens divided by the total number of successful deliveries.", + "readOnly": true + }, + "clicks": { + "type": "integer", + "title": "Total Clicks", + "description": "The total number of clicks for an campaign.", + "readOnly": true + }, + "subscriber_clicks": { + "type": "integer", + "title": "Unique Subscriber Clicks", + "description": "The number of unique clicks.", + "readOnly": true + }, + "click_rate": { + "type": "number", + "title": "Click Rate", + "description": "The number of unique clicks divided by the total number of successful deliveries.", + "readOnly": true + }, + "ecommerce": { + "type": "object", + "title": "E-Commerce Report", + "description": "E-Commerce stats for a campaign.", + "properties": { + "total_orders": { + "type": "integer", + "title": "Total Orders", + "description": "The total orders for a campaign.", + "readOnly": true + }, + "total_spent": { + "type": "number", + "title": "Total Spent", + "description": "The total spent for a campaign. Calculated as the sum of all order totals with no deductions.", + "readOnly": true + }, + "total_revenue": { + "type": "number", + "title": "Total Revenue", + "description": "The total revenue for a campaign. Calculated as the sum of all order totals minus shipping and tax totals.", + "readOnly": true + } + } + } + } + }, + "delivery_status": { + "type": "object", + "title": "Campaign Delivery Status", + "description": "Updates on campaigns in the process of sending.", + "properties": { + "enabled": { + "type": "boolean", + "title": "Delivery Status Enabled", + "description": "Whether Campaign Delivery Status is enabled for this account and campaign.", + "readOnly": true + }, + "can_cancel": { + "type": "boolean", + "title": "Campaign Cancelable", + "description": "Whether a campaign send can be canceled.", + "readOnly": true + }, + "status": { + "type": "string", + "title": "Campaign Delivery Status", + "description": "The current state of a campaign delivery.", + "enum": ["delivering", "delivered", "canceling", "canceled"], + "readOnly": true + }, + "emails_sent": { + "type": "integer", + "title": "Emails Sent", + "description": "The total number of emails confirmed sent for this campaign so far.", + "readOnly": true + }, + "emails_canceled": { + "type": "integer", + "title": "Emails Canceled", + "description": "The total number of emails canceled for this campaign.", + "readOnly": true + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/schemas/email_activity.json b/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/schemas/email_activity.json new file mode 100644 index 0000000000000..af93cb761c0ba --- /dev/null +++ b/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/schemas/email_activity.json @@ -0,0 +1,60 @@ +{ + "type": "object", + "title": "Email Activity", + "description": "A list of member's subscriber activity in a specific campaign.", + "properties": { + "campaign_id": { + "type": "string", + "title": "The unique id for the campaign.", + "description": "The unique id for the campaign." + }, + "list_id": { + "type": "string", + "title": "The unique id for the list.", + "description": "The unique id for the list." + }, + "list_is_active": { + "type": "boolean", + "title": "The status of the list used.", + "description": "The status of the list used, namely if it's deleted or disabled." + }, + "email_id": { + "type": "string", + "title": "email MD5 hash.", + "description": "The MD5 hash of the lowercase version of the list member's email address." + }, + "email_address": { + "type": "string", + "title": "Email address for a subscriber.", + "description": "Email address for a subscriber." + }, + "action": { + "type": "string", + "title": "action", + "enum": ["open", "click", "bounce"], + "description": "One of the following actions: 'open', 'click', or 'bounce'" + }, + "type": { + "type": "string", + "title": "Type", + "enum": ["hard", "soft"], + "description": "If the action is a 'bounce', the type of bounce received: 'hard', 'soft'." + }, + "timestamp": { + "type": "string", + "title": "Action date and time", + "description": "The date and time recorded for the action in ISO 8601 format.", + "format": "date-time" + }, + "url": { + "type": "string", + "title": "The IP address.", + "description": "The IP address recorded for the action." + }, + "ip": { + "type": "string", + "title": "Action ip address", + "description": "The IP address recorded for the action." + } + } +} diff --git a/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/schemas/lists.json b/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/schemas/lists.json new file mode 100644 index 0000000000000..2137f60da131a --- /dev/null +++ b/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/schemas/lists.json @@ -0,0 +1,304 @@ +{ + "type": "object", + "title": "Subscriber List", + "description": "Information about a specific list.", + "properties": { + "id": { + "type": "string", + "title": "List ID", + "description": "A string that uniquely identifies this list.", + "readOnly": true + }, + "web_id": { + "type": "integer", + "title": "List Web ID", + "description": "The ID used in the Mailchimp web application. View this list in your Mailchimp account at `https://{dc}.admin.mailchimp.com/lists/members/?id={web_id}`.", + "readOnly": true + }, + "name": { + "type": "string", + "title": "List Name", + "description": "The name of the list." + }, + "contact": { + "type": "object", + "title": "List Contact", + "description": "[Contact information displayed in campaign footers](https://mailchimp.com/help/about-campaign-footers/) to comply with international spam laws.", + "properties": { + "company": { + "type": "string", + "title": "Company Name", + "description": "The company name for the list." + }, + "address1": { + "type": "string", + "title": "Address", + "description": "The street address for the list contact." + }, + "address2": { + "type": "string", + "title": "Address", + "description": "The street address for the list contact." + }, + "city": { + "type": "string", + "title": "City", + "description": "The city for the list contact." + }, + "state": { + "type": "string", + "title": "State", + "description": "The state for the list contact." + }, + "zip": { + "type": "string", + "title": "Postal Code", + "description": "The postal or zip code for the list contact." + }, + "country": { + "type": "string", + "title": "Country Code", + "description": "A two-character ISO3166 country code. Defaults to US if invalid." + }, + "phone": { + "type": "string", + "title": "Phone Number", + "description": "The phone number for the list contact." + } + } + }, + "permission_reminder": { + "type": "string", + "title": "Permission Reminder", + "description": "The [permission reminder](https://mailchimp.com/help/edit-the-permission-reminder/) for the list." + }, + "use_archive_bar": { + "type": "boolean", + "title": "Use Archive Bar", + "description": "Whether campaigns for this list use the [Archive Bar](https://mailchimp.com/help/about-email-campaign-archives-and-pages/) in archives by default.", + "default": false + }, + "campaign_defaults": { + "type": "object", + "title": "Campaign Defaults", + "description": "[Default values for campaigns](https://mailchimp.com/help/edit-your-emails-subject-preview-text-from-name-or-from-email-address/) created for this list.", + "properties": { + "from_name": { + "type": "string", + "title": "Sender's Name", + "description": "The default from name for campaigns sent to this list." + }, + "from_email": { + "type": "string", + "title": "Sender's Email Address", + "description": "The default from email for campaigns sent to this list." + }, + "subject": { + "type": "string", + "title": "Subject", + "description": "The default subject line for campaigns sent to this list." + }, + "language": { + "type": "string", + "title": "Language", + "description": "The default language for this lists's forms." + } + } + }, + "notify_on_subscribe": { + "type": "string", + "title": "Notify on Subscribe", + "description": "The email address to send [subscribe notifications](https://mailchimp.com/help/change-subscribe-and-unsubscribe-notifications/) to.", + "default": false + }, + "notify_on_unsubscribe": { + "type": "string", + "title": "Notify on Unsubscribe", + "description": "The email address to send [unsubscribe notifications](https://mailchimp.com/help/change-subscribe-and-unsubscribe-notifications/) to.", + "default": false + }, + "date_created": { + "type": "string", + "title": "Creation Date", + "description": "The date and time that this list was created in ISO 8601 format.", + "format": "date-time", + "readOnly": true + }, + "list_rating": { + "type": "integer", + "title": "List Rating", + "description": "An auto-generated activity score for the list (0-5).", + "readOnly": true + }, + "email_type_option": { + "type": "boolean", + "title": "Email Type Option", + "description": "Whether the list supports [multiple formats for emails](https://mailchimp.com/help/change-list-name-and-defaults/). When set to `true`, subscribers can choose whether they want to receive HTML or plain-text emails. When set to `false`, subscribers will receive HTML emails, with a plain-text alternative backup." + }, + "subscribe_url_short": { + "type": "string", + "title": "Subscribe URL Short", + "description": "Our [EepURL shortened](https://mailchimp.com/help/share-your-signup-form/) version of this list's subscribe form.", + "readOnly": true + }, + "subscribe_url_long": { + "type": "string", + "title": "Subscribe URL Long", + "description": "The full version of this list's subscribe form (host will vary).", + "readOnly": true + }, + "beamer_address": { + "type": "string", + "title": "Beamer Address", + "description": "The list's [Email Beamer](https://mailchimp.com/help/use-email-beamer-to-create-a-campaign/) address.", + "readOnly": true + }, + "visibility": { + "type": "string", + "title": "Visibility", + "enum": ["pub", "prv"], + "description": "Whether this list is [public or private](https://mailchimp.com/help/about-list-publicity/)." + }, + "double_optin": { + "type": "boolean", + "title": "Double Opt In", + "description": "Whether or not to require the subscriber to confirm subscription via email.", + "default": false + }, + "has_welcome": { + "type": "boolean", + "title": "Has Welcome", + "description": "Whether or not this list has a welcome automation connected. Welcome Automations: welcomeSeries, singleWelcome, emailFollowup.", + "default": false, + "example": false + }, + "marketing_permissions": { + "type": "boolean", + "title": "Marketing Permissions", + "description": "Whether or not the list has marketing permissions (eg. GDPR) enabled.", + "default": false + }, + "modules": { + "type": "array", + "title": "Modules", + "description": "Any list-specific modules installed for this list.", + "items": { + "type": "string" + }, + "readOnly": true + }, + "stats": { + "type": "object", + "title": "Statistics", + "description": "Stats for the list. Many of these are cached for at least five minutes.", + "readOnly": true, + "properties": { + "member_count": { + "type": "integer", + "title": "Member Count", + "description": "The number of active members in the list.", + "readOnly": true + }, + "total_contacts": { + "type": "integer", + "title": "Total Contacts", + "description": "The number of contacts in the list, including subscribed, unsubscribed, pending, cleaned, deleted, transactional, and those that need to be reconfirmed.", + "readOnly": true + }, + "unsubscribe_count": { + "type": "integer", + "title": "Unsubscribe Count", + "description": "The number of members who have unsubscribed from the list.", + "readOnly": true + }, + "cleaned_count": { + "type": "integer", + "title": "Cleaned Count", + "description": "The number of members cleaned from the list.", + "readOnly": true + }, + "member_count_since_send": { + "type": "integer", + "title": "Member Count Since Send", + "description": "The number of active members in the list since the last campaign was sent.", + "readOnly": true + }, + "unsubscribe_count_since_send": { + "type": "integer", + "title": "Unsubscribe Count Since Send", + "description": "The number of members who have unsubscribed since the last campaign was sent.", + "readOnly": true + }, + "cleaned_count_since_send": { + "type": "integer", + "title": "Cleaned Count Since Send", + "description": "The number of members cleaned from the list since the last campaign was sent.", + "readOnly": true + }, + "campaign_count": { + "type": "integer", + "title": "Campaign Count", + "description": "The number of campaigns in any status that use this list.", + "readOnly": true + }, + "campaign_last_sent": { + "type": "string", + "format": "date-time", + "title": "Campaign Last Sent", + "description": "The date and time the last campaign was sent to this list in ISO 8601 format. This is updated when a campaign is sent to 10 or more recipients.", + "readOnly": true + }, + "merge_field_count": { + "type": "integer", + "title": "Merge Var Count", + "description": "The number of merge vars for this list (not EMAIL, which is required).", + "readOnly": true + }, + "avg_sub_rate": { + "type": "number", + "title": "Average Subscription Rate", + "description": "The average number of subscriptions per month for the list (not returned if we haven't calculated it yet).", + "readOnly": true + }, + "avg_unsub_rate": { + "type": "number", + "title": "Average Unsubscription Rate", + "description": "The average number of unsubscriptions per month for the list (not returned if we haven't calculated it yet).", + "readOnly": true + }, + "target_sub_rate": { + "type": "number", + "title": "Average Subscription Rate", + "description": "The target number of subscriptions per month for the list to keep it growing (not returned if we haven't calculated it yet).", + "readOnly": true + }, + "open_rate": { + "type": "number", + "title": "Open Rate", + "description": "The average open rate (a percentage represented as a number between 0 and 100) per campaign for the list (not returned if we haven't calculated it yet).", + "readOnly": true + }, + "click_rate": { + "type": "number", + "title": "Click Rate", + "description": "The average click rate (a percentage represented as a number between 0 and 100) per campaign for the list (not returned if we haven't calculated it yet).", + "readOnly": true + }, + "last_sub_date": { + "type": "string", + "format": "date-time", + "title": "Date of Last List Subscribe", + "description": "The date and time of the last time someone subscribed to this list in ISO 8601 format.", + "readOnly": true + }, + "last_unsub_date": { + "type": "string", + "format": "date-time", + "title": "Date of Last List Unsubscribe", + "description": "The date and time of the last time someone unsubscribed from this list in ISO 8601 format.", + "readOnly": true + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/source.py b/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/source.py index 3c8f94fe5f685..67552a7e3d7d5 100644 --- a/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/source.py +++ b/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/source.py @@ -23,70 +23,36 @@ # -import json -from typing import DefaultDict, Dict, Generator +import base64 +from typing import Any, List, Mapping, Tuple -from airbyte_protocol import ( - AirbyteCatalog, - AirbyteConnectionStatus, - AirbyteMessage, - ConfiguredAirbyteCatalog, - ConfiguredAirbyteStream, - Status, - SyncMode, -) -from base_python import AirbyteLogger, Source +from airbyte_cdk import AirbyteLogger +from airbyte_cdk.sources import AbstractSource +from airbyte_cdk.sources.streams import Stream +from airbyte_cdk.sources.streams.http.auth import TokenAuthenticator +from mailchimp3 import MailChimp -from .client import Client +from .streams import Campaigns, EmailActivity, Lists -class SourceMailchimp(Source): - """ - Mailchimp API Reference: https://mailchimp.com/developer/api/ - """ +class HttpBasicAuthenticator(TokenAuthenticator): + def __init__(self, auth: Tuple[str, str], auth_method: str = "Basic", **kwargs): + auth_string = f"{auth[0]}:{auth[1]}".encode("utf8") + b64_encoded = base64.b64encode(auth_string).decode("utf8") + super().__init__(token=b64_encoded, auth_method=auth_method, **kwargs) - def check(self, logger: AirbyteLogger, config: json) -> AirbyteConnectionStatus: - client = self._client(config) - alive, error = client.health_check() - if not alive: - return AirbyteConnectionStatus(status=Status.FAILED, message=f"{error.title}: {error.detail}") - return AirbyteConnectionStatus(status=Status.SUCCEEDED) +class SourceMailchimp(AbstractSource): + def check_connection(self, logger: AirbyteLogger, config: Mapping[str, Any]) -> Tuple[bool, Any]: + try: + client = MailChimp(mc_api=config["apikey"], mc_user=config["username"]) + client.ping.get() + return True, None + except Exception as e: + return False, repr(e) - def discover(self, logger: AirbyteLogger, config: json) -> AirbyteCatalog: - client = self._client(config) + def streams(self, config: Mapping[str, Any]) -> List[Stream]: + authenticator = HttpBasicAuthenticator(auth=("anystring", config["apikey"])) + streams_ = [Lists(authenticator=authenticator), Campaigns(authenticator=authenticator), EmailActivity(authenticator=authenticator)] - return AirbyteCatalog(streams=client.get_streams()) - - def read( - self, logger: AirbyteLogger, config: json, catalog: ConfiguredAirbyteCatalog, state: Dict[str, any] - ) -> Generator[AirbyteMessage, None, None]: - client = self._client(config) - - logger.info("Starting syncing mailchimp") - for configured_stream in catalog.streams: - yield from self._read_record(client=client, configured_stream=configured_stream, state=state) - - logger.info("Finished syncing mailchimp") - - def _client(self, config: json): - client = Client(username=config["username"], apikey=config["apikey"]) - - return client - - @staticmethod - def _read_record( - client: Client, configured_stream: ConfiguredAirbyteStream, state: DefaultDict[str, any] - ) -> Generator[AirbyteMessage, None, None]: - entity_map = { - "Lists": client.lists, - "Campaigns": client.campaigns, - } - - stream_name = configured_stream.stream.name - - if configured_stream.sync_mode == SyncMode.full_refresh: - state.pop(stream_name, None) - - for record in entity_map[stream_name](state=state): - yield record + return streams_ diff --git a/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/streams.py b/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/streams.py new file mode 100644 index 0000000000000..5d7d8d6d225ce --- /dev/null +++ b/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/streams.py @@ -0,0 +1,180 @@ +# +# 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. +# + + +import math +from abc import ABC, abstractmethod +from typing import Any, Iterable, Mapping, MutableMapping, Optional + +import requests +from airbyte_cdk.models import SyncMode +from airbyte_cdk.sources.streams.http import HttpStream + + +class MailChimpStream(HttpStream, ABC): + url_base = "https://us2.api.mailchimp.com/3.0/" + primary_key = "id" + page_size = 100 + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.current_offset = 0 + + def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]: + decoded_response = response.json() + api_data = decoded_response[self.data_field] + if len(api_data) < self.page_size: + self.current_offset = 0 + return None + else: + self.current_offset += self.page_size + return {"offset": self.current_offset} + + def request_params( + self, + stream_state: Mapping[str, Any], + stream_slice: Mapping[str, Any] = None, + next_page_token: Mapping[str, Any] = None, + ) -> MutableMapping[str, Any]: + + params = {"count": self.page_size} + + # Handle pagination by inserting the next page's token in the request parameters + if next_page_token: + params.update(next_page_token) + return params + + def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]: + response_json = response.json() + yield from response_json[self.data_field] + + @property + @abstractmethod + def data_field(self) -> str: + """the responce entry that contains useful data""" + pass + + +class IncrementalMailChimpStream(MailChimpStream, ABC): + state_checkpoint_interval = math.inf + + @property + @abstractmethod + def cursor_field(self) -> str: + """ + Defining a cursor field indicates that a stream is incremental, so any incremental stream must extend this class + and define a cursor field. + """ + pass + + def get_updated_state(self, current_stream_state: MutableMapping[str, Any], latest_record: Mapping[str, Any]) -> Mapping[str, Any]: + """ + Return the latest state by comparing the cursor value in the latest record with the stream's most recent state object + and returning an updated state object. + """ + latest_state = latest_record.get(self.cursor_field) + current_state = current_stream_state.get(self.cursor_field) or latest_state + return {self.cursor_field: max(latest_state, current_state)} + + def request_params(self, stream_state=None, **kwargs): + stream_state = stream_state or {} + params = super().request_params(stream_state=stream_state, **kwargs) + default_params = {"sort_field": self.cursor_field, "sort_dir": "ASC"} + since_value = stream_state.get(self.cursor_field) + if since_value: + default_params[f"since_{self.cursor_field}"] = since_value + params.update(default_params) + return params + + +class Lists(IncrementalMailChimpStream): + cursor_field = "date_created" + data_field = "lists" + + def path(self, **kwargs) -> str: + return "lists" + + +class Campaigns(IncrementalMailChimpStream): + cursor_field = "create_time" + data_field = "campaigns" + + def path(self, **kwargs) -> str: + return "campaigns" + + +class EmailActivity(IncrementalMailChimpStream): + cursor_field = "timestamp" + data_field = "emails" + + def stream_slices(self, **kwargs): + campaign_stream = Campaigns(authenticator=self.authenticator) + for campaign in campaign_stream.read_records(sync_mode=SyncMode.full_refresh): + yield {"campaign_id": campaign["id"]} + + def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: + campaign_id = stream_slice["campaign_id"] + return f"reports/{campaign_id}/email-activity" + + def get_updated_state(self, current_stream_state: MutableMapping[str, Any], latest_record: Mapping[str, Any]) -> Mapping[str, Any]: + """ + Return the latest state by comparing the campaign_id and cursor value in the latest record with the stream's most recent state object + and returning an updated state object. + """ + campaign_id = latest_record.get("campaign_id") + latest_cursor_value = latest_record.get(self.cursor_field) + current_stream_state = current_stream_state or {} + current_state = current_stream_state.get(campaign_id) if current_stream_state else None + if current_state: + current_state = current_state.get(self.cursor_field) + current_state_value = current_state or latest_cursor_value + max_value = max(current_state_value, latest_cursor_value) + new_value = {self.cursor_field: max_value} + + current_stream_state[campaign_id] = new_value + return current_stream_state + + def request_params(self, stream_state=None, stream_slice: Mapping[str, Any] = None, **kwargs): + stream_state = stream_state or {} + params = MailChimpStream.request_params(self, stream_state=stream_state, **kwargs) + + since_value_camp = stream_state.get(stream_slice["campaign_id"]) + if since_value_camp: + since_value = since_value_camp.get(self.cursor_field) + if since_value: + params["since"] = since_value + return params + + def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]: + response_json = response.json() + # transform before save + # [{'campaign_id', 'list_id', 'list_is_active', 'email_id', 'email_address', 'activity[array[object]]', '_links'}] -> + # -> [[{'campaign_id', 'list_id', 'list_is_active', 'email_id', 'email_address', '**activity[i]', '_links'}, ...]] + data = response_json[self.data_field] + for item in data: + for activity_record in item["activity"]: + new_record = {k: v for k, v in item.items() if k != "activity"} + for k, v in activity_record.items(): + new_record[k] = v + yield new_record diff --git a/airbyte-integrations/connectors/source-mailchimp/unit_tests/unit_test.py b/airbyte-integrations/connectors/source-mailchimp/unit_tests/unit_test.py index b503858068046..a78fea7148fe0 100644 --- a/airbyte-integrations/connectors/source-mailchimp/unit_tests/unit_test.py +++ b/airbyte-integrations/connectors/source-mailchimp/unit_tests/unit_test.py @@ -23,10 +23,11 @@ # -import pytest -from source_mailchimp.client import Client +from airbyte_cdk.logger import AirbyteLogger +from source_mailchimp import SourceMailchimp def test_client_wrong_credentials(): - with pytest.raises(ValueError, match="The API key that you have entered is not valid"): - Client(username="unknown_user", apikey="wrong_key") + source = SourceMailchimp() + status, error = source.check_connection(logger=AirbyteLogger, config={"username": "Jonny", "apikey": "blah-blah"}) + assert not status