diff --git a/airbyte-config/init/src/main/resources/config/STANDARD_SOURCE_DEFINITION/d8313939-3782-41b0-be29-b3ca20d8dd3a.json b/airbyte-config/init/src/main/resources/config/STANDARD_SOURCE_DEFINITION/d8313939-3782-41b0-be29-b3ca20d8dd3a.json index 500de9798fde..feeedd810bac 100644 --- a/airbyte-config/init/src/main/resources/config/STANDARD_SOURCE_DEFINITION/d8313939-3782-41b0-be29-b3ca20d8dd3a.json +++ b/airbyte-config/init/src/main/resources/config/STANDARD_SOURCE_DEFINITION/d8313939-3782-41b0-be29-b3ca20d8dd3a.json @@ -1,8 +1,8 @@ { "sourceDefinitionId": "d8313939-3782-41b0-be29-b3ca20d8dd3a", "name": "Intercom", - "dockerRepository": "airbyte/source-intercom-singer", - "dockerImageTag": "0.2.3", - "documentationUrl": "https://hub.docker.com/r/airbyte/source-intercom-singer", + "dockerRepository": "airbyte/source-intercom", + "dockerImageTag": "0.1.0", + "documentationUrl": "https://hub.docker.com/r/airbyte/source-intercom", "icon": "intercom.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 be08178f40ac..20eb19d2a397 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -184,9 +184,9 @@ icon: zendesk.svg - sourceDefinitionId: d8313939-3782-41b0-be29-b3ca20d8dd3a name: Intercom - dockerRepository: airbyte/source-intercom-singer - dockerImageTag: 0.2.3 - documentationUrl: https://hub.docker.com/r/airbyte/source-intercom-singer + dockerRepository: airbyte/source-intercom + dockerImageTag: 0.1.0 + documentationUrl: https://hub.docker.com/r/airbyte/source-intercom icon: intercom.svg - sourceDefinitionId: 68e63de2-bb83-4c7e-93fa-a8a9051e3993 name: Jira diff --git a/airbyte-integrations/builds.md b/airbyte-integrations/builds.md index 423ecb7b1ae9..4d7ae5ee6c8f 100644 --- a/airbyte-integrations/builds.md +++ b/airbyte-integrations/builds.md @@ -55,7 +55,7 @@ Instagram [![source-instagram](https://img.shields.io/endpoint?url=https%3A%2F%2Fstatus-api.airbyte.io%2Ftests%2Fsummary%2Fsource-instagram%2Fbadge.json)](https://status-api.airbyte.io/tests/summary/source-instagram) - Intercom [![source-intercom-singer](https://img.shields.io/endpoint?url=https%3A%2F%2Fstatus-api.airbyte.io%2Ftests%2Fsummary%2Fsource-intercom-singer%2Fbadge.json)](https://status-api.airbyte.io/tests/summary/source-intercom-singer) + Intercom [![source-intercom](https://img.shields.io/endpoint?url=https%3A%2F%2Fstatus-api.airbyte.io%2Ftests%2Fsummary%2Fsource-intercom-singer%2Fbadge.json)](https://status-api.airbyte.io/tests/summary/source-intercom) Iterable [![source-iterable](https://img.shields.io/endpoint?url=https%3A%2F%2Fstatus-api.airbyte.io%2Ftests%2Fsummary%2Fsource-iterable%2Fbadge.json)](https://status-api.airbyte.io/tests/summary/source-iterable) diff --git a/airbyte-integrations/connectors/source-intercom/.dockerignore b/airbyte-integrations/connectors/source-intercom/.dockerignore new file mode 100644 index 000000000000..e06cadbdaf9f --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/.dockerignore @@ -0,0 +1,7 @@ +* +!Dockerfile +!Dockerfile.test +!main.py +!source_intercom +!setup.py +!secrets diff --git a/airbyte-integrations/connectors/source-intercom/Dockerfile b/airbyte-integrations/connectors/source-intercom/Dockerfile new file mode 100644 index 000000000000..fd3069a3bb2f --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/Dockerfile @@ -0,0 +1,16 @@ +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/* + +WORKDIR /airbyte/integration_code +COPY source_intercom ./source_intercom +COPY main.py ./ +COPY setup.py ./ +RUN pip install . + +ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" +ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] + +LABEL io.airbyte.version=0.1.0 +LABEL io.airbyte.name=airbyte/source-intercom diff --git a/airbyte-integrations/connectors/source-intercom/README.md b/airbyte-integrations/connectors/source-intercom/README.md new file mode 100644 index 000000000000..356e26dd6c39 --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/README.md @@ -0,0 +1,131 @@ +# Intercom Source + +This is the repository for the Intercom source connector, written in Python. +For information about how to use this connector within Airbyte, see [the documentation](https://docs.airbyte.io/integrations/sources/intercom). + +## Local development + +### Prerequisites +**To iterate on this connector, make sure to complete this prerequisites section.** + +#### Minimum Python version required `= 3.7.0` + +#### Build & Activate Virtual Environment and install dependencies +From this connector directory, create a virtual environment: +``` +python -m venv .venv +``` + +This will generate a virtualenv for this module in `.venv/`. Make sure this venv is active in your +development environment of choice. To activate it from the terminal, run: +``` +source .venv/bin/activate +pip install -r requirements.txt +``` +If you are in an IDE, follow your IDE's instructions to activate the virtualenv. + +Note that while we are installing dependencies from `requirements.txt`, you should only edit `setup.py` for your dependencies. `requirements.txt` is +used for editable installs (`pip install -e`) to pull in Python dependencies from the monorepo and will call `setup.py`. +If this is mumbo jumbo to you, don't worry about it, just put your deps in `setup.py` but install using `pip install -r requirements.txt` and everything +should work as you expect. + +#### Building via Gradle +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-intercom:build +``` + +#### Create credentials +**If you are a community contributor**, follow the instructions in the [documentation](https://docs.airbyte.io/integrations/sources/intercom) +to generate the necessary credentials. Then create a file `secrets/config.json` conforming to the `source_intercom/spec.json` file. +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 `integration_tests/sample_config.json` for a sample config file. + +**If you are an Airbyte core member**, copy the credentials in Lastpass under the secret name `source intercom test creds` +and place them into `secrets/config.json`. + +### Locally running the connector +``` +python main.py spec +python main.py check --config secrets/config.json +python main.py discover --config secrets/config.json +python main.py read --config secrets/config.json --catalog integration_tests/configured_catalog.json +``` + +### Locally running the connector docker image + +#### Build +First, make sure you build the latest Docker image: +``` +docker build . -t airbyte/source-intercom:dev +``` + +You can also build the connector image via Gradle: +``` +./gradlew :airbyte-integrations:connectors:source-intercom:airbyteDocker +``` +When building via Gradle, the docker image name and tag, respectively, are the values of the `io.airbyte.name` and `io.airbyte.version` `LABEL`s in +the Dockerfile. + +#### Run +Then run any of the connector commands as follows: +``` +docker run --rm airbyte/source-intercom:dev spec +docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-intercom:dev check --config /secrets/config.json +docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-intercom:dev discover --config /secrets/config.json +docker run --rm -v $(pwd)/secrets:/secrets -v $(pwd)/integration_tests:/integration_tests airbyte/source-intercom:dev read --config /secrets/config.json --catalog /integration_tests/configured_catalog.json +``` +## Testing +Make sure to familiarize yourself with [pytest test discovery](https://docs.pytest.org/en/latest/goodpractices.html#test-discovery) to know how your test files and methods should be named. +First install test dependencies into your virtual environment: +``` +pip install .[tests] +``` +### Unit Tests +To run unit tests locally, from the connector directory run: +``` +python -m pytest unit_tests +``` + +### Integration Tests +There are two types of integration tests: Acceptance Tests (Airbyte's test suite for all source connectors) and custom integration tests (which are specific to this connector). +#### Custom Integration tests +Place custom tests inside `integration_tests/` folder, then, from the connector root, run +``` +python -m pytest integration_tests +``` +#### Acceptance Tests +Customize `acceptance-test-config.yml` file to configure tests. See [Source Acceptance Tests](source-acceptance-tests.md) for more information. +If your connector requires to create or destroy resources for use during acceptance tests create fixtures for it and place them inside integration_tests/acceptance.py. +To run your integration tests with acceptance tests, from the connector root, run +``` +python -m pytest integration_tests -p integration_tests.acceptance +``` +To run your integration tests with docker + +### Using gradle to run tests +All commands should be run from airbyte project root. +To run unit tests: +``` +./gradlew :airbyte-integrations:connectors:source-intercom:unitTest +``` +To run acceptance and custom integration tests: +``` +./gradlew :airbyte-integrations:connectors:source-intercom:integrationTest +``` + +## Dependency Management +All of your dependencies should go in `setup.py`, NOT `requirements.txt`. The requirements file is only used to connect internal Airbyte dependencies in the monorepo for local development. +We split dependencies between two groups, dependencies that are: +* required for your connector to work need to go to `MAIN_REQUIREMENTS` list. +* required for the testing need to go to `TEST_REQUIREMENTS` list + +### Publishing a new version of the connector +You've checked out the repo, implemented a million dollar feature, and you're ready to share your changes with the world. Now what? +1. Make sure your changes are passing unit and integration tests. +1. Bump the connector version in `Dockerfile` -- just increment the value of the `LABEL io.airbyte.version` appropriately (we use [SemVer](https://semver.org/)). +1. Create a Pull Request. +1. Pat yourself on the back for being an awesome contributor. +1. Someone from Airbyte will take a look at your PR and iterate with you to merge it into master. diff --git a/airbyte-integrations/connectors/source-intercom/acceptance-test-config.yml b/airbyte-integrations/connectors/source-intercom/acceptance-test-config.yml new file mode 100644 index 000000000000..0843f6349f8e --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/acceptance-test-config.yml @@ -0,0 +1,31 @@ +# See [Source Acceptance Tests](https://docs.airbyte.io/contributing-to-airbyte/building-new-connector/source-acceptance-tests.md) +# for more information about how to configure these tests +connector_image: airbyte/source-intercom:dev +tests: + spec: + - spec_path: "source_intercom/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 + incremental: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" + future_state_path: "integration_tests/abnormal_state.json" + cursor_paths: + companies: ["updated_at"] + company_segments: ["updated_at"] + conversations: ["updated_at"] + conversation_parts: ["updated_at"] + contacts: ["updated_at"] + segments: ["updated_at"] + full_refresh: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-intercom/acceptance-test-docker.sh b/airbyte-integrations/connectors/source-intercom/acceptance-test-docker.sh new file mode 100755 index 000000000000..1425ff74f151 --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/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-intercom/build.gradle b/airbyte-integrations/connectors/source-intercom/build.gradle new file mode 100644 index 000000000000..78d760d044a1 --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/build.gradle @@ -0,0 +1,14 @@ +plugins { + id 'airbyte-python' + id 'airbyte-docker' + id 'airbyte-source-acceptance-test' +} + +airbytePython { + moduleDirectory 'source_intercom' +} + +dependencies { + implementation files(project(':airbyte-integrations:bases:source-acceptance-test').airbyteDocker.outputs) + implementation files(project(':airbyte-integrations:bases:base-python').airbyteDocker.outputs) +} diff --git a/airbyte-integrations/connectors/source-intercom/integration_tests/__init__.py b/airbyte-integrations/connectors/source-intercom/integration_tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/airbyte-integrations/connectors/source-intercom/integration_tests/abnormal_state.json b/airbyte-integrations/connectors/source-intercom/integration_tests/abnormal_state.json new file mode 100644 index 000000000000..1bb4ec8dd2e0 --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/integration_tests/abnormal_state.json @@ -0,0 +1,20 @@ +{ + "companies": { + "updated_at": "2022-07-12T10:44:09+00:00" + }, + "company_segments": { + "updated_at": "2022-07-12T10:44:09+00:00" + }, + "conversations": { + "updated_at": "2022-07-12T10:44:09+00:00" + }, + "conversation_parts": { + "updated_at": "2022-07-12T10:44:09+00:00" + }, + "contacts": { + "updated_at": "2022-07-12T10:44:09+00:00" + }, + "segments": { + "updated_at": "2022-07-12T10:44:09+00:00" + } +} diff --git a/airbyte-integrations/connectors/source-intercom/integration_tests/acceptance.py b/airbyte-integrations/connectors/source-intercom/integration_tests/acceptance.py new file mode 100644 index 000000000000..496a799cf8ed --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/integration_tests/acceptance.py @@ -0,0 +1,33 @@ +# +# 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 pytest + +pytest_plugins = ("source_acceptance_test.plugin",) + + +@pytest.fixture(scope="session", autouse=True) +def connector_setup(): + yield diff --git a/airbyte-integrations/connectors/source-intercom/integration_tests/catalog.json b/airbyte-integrations/connectors/source-intercom/integration_tests/catalog.json new file mode 100644 index 000000000000..38ca3d8e410f --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/integration_tests/catalog.json @@ -0,0 +1,181 @@ +{ + "streams": [ + { + "name": "admins", + "json_schema": { + "properties": { + "admin_ids": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "integer" + } + }, + { + "type": "null" + } + ] + }, + "avatar": { + "properties": { + "image_url": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "away_mode_enabled": { + "type": ["null", "boolean"] + }, + "away_mode_reassign": { + "type": ["null", "boolean"] + }, + "email": { + "type": ["null", "string"] + }, + "has_inbox_seat": { + "type": ["null", "boolean"] + }, + "id": { + "type": ["null", "string"] + }, + "job_title": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "team_ids": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "integer" + } + }, + { + "type": "null" + } + ] + }, + "type": { + "type": ["null", "string"] + } + }, + "type": "object", + "additionalProperties": false + } + }, + { + "name": "companies", + "json_schema": { + "properties": { + "company_id": { + "type": ["null", "string"] + }, + "created_at": { + "format": "date-time", + "type": ["null", "string"] + }, + "custom_attributes": { + "type": ["null", "object"], + "additionalProperties": true + }, + "id": { + "type": ["null", "string"] + }, + "industry": { + "type": ["null", "string"] + }, + "monthly_spend": { + "multipleOf": 1e-8, + "type": ["null", "number"] + }, + "name": { + "type": ["null", "string"] + }, + "plan": { + "properties": { + "id": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "remote_created_at": { + "format": "date-time", + "type": ["null", "string"] + }, + "segments": { + "anyOf": [ + { + "type": "array", + "items": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "id": { + "type": ["null", "string"] + } + } + } + }, + { + "type": "null" + } + ] + }, + "session_count": { + "type": ["null", "integer"] + }, + "size": { + "type": ["null", "integer"] + }, + "tags": { + "anyOf": [ + { + "type": "array", + "items": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "id": { + "type": ["null", "string"] + } + } + } + }, + { + "type": "null" + } + ] + }, + "type": { + "type": ["null", "string"] + }, + "updated_at": { + "format": "date-time", + "type": ["null", "string"] + }, + "user_count": { + "type": ["null", "integer"] + }, + "website": { + "type": ["null", "string"] + } + }, + "type": "object", + "additionalProperties": false + } + } + ] +} diff --git a/airbyte-integrations/connectors/source-intercom/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-intercom/integration_tests/configured_catalog.json new file mode 100644 index 000000000000..e6dc584fdbf9 --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/integration_tests/configured_catalog.json @@ -0,0 +1,1339 @@ +{ + "streams": [ + { + "stream": { + "name": "admins", + "json_schema": { + "properties": { + "admin_ids": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "integer" + } + }, + { + "type": "null" + } + ] + }, + "avatar": { + "properties": { + "image_url": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "away_mode_enabled": { + "type": ["null", "boolean"] + }, + "away_mode_reassign": { + "type": ["null", "boolean"] + }, + "email": { + "type": ["null", "string"] + }, + "has_inbox_seat": { + "type": ["null", "boolean"] + }, + "id": { + "type": ["null", "string"] + }, + "job_title": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "team_ids": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "integer" + } + }, + { + "type": "null" + } + ] + }, + "type": { + "type": ["null", "string"] + } + }, + "type": "object", + "additionalProperties": false + }, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "companies", + "json_schema": { + "properties": { + "company_id": { + "type": ["null", "string"] + }, + "created_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "custom_attributes": { + "type": ["null", "object"], + "additionalProperties": true + }, + "id": { + "type": ["null", "string"] + }, + "industry": { + "type": ["null", "string"] + }, + "monthly_spend": { + "multipleOf": 1e-8, + "type": ["null", "number"] + }, + "name": { + "type": ["null", "string"] + }, + "plan": { + "properties": { + "id": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "remote_created_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "segments": { + "anyOf": [ + { + "type": "array", + "items": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "id": { + "type": ["null", "string"] + } + } + } + }, + { + "type": "null" + } + ] + }, + "session_count": { + "type": ["null", "integer"] + }, + "size": { + "type": ["null", "integer"] + }, + "tags": { + "anyOf": [ + { + "type": "array", + "items": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "id": { + "type": ["null", "string"] + } + } + } + }, + { + "type": "null" + } + ] + }, + "type": { + "type": ["null", "string"] + }, + "updated_at": { + "type": ["null", "integer"] + }, + "user_count": { + "type": ["null", "integer"] + }, + "website": { + "type": ["null", "string"] + } + }, + "type": "object", + "additionalProperties": false + }, + "supported_sync_modes": ["full_refresh", "incremental"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "company_attributes", + "json_schema": { + "properties": { + "admin_id": { + "type": ["null", "string"] + }, + "api_writable": { + "type": ["null", "boolean"] + }, + "archived": { + "type": ["null", "boolean"] + }, + "created_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "custom": { + "type": ["null", "boolean"] + }, + "data_type": { + "type": ["null", "string"] + }, + "description": { + "type": ["null", "string"] + }, + "full_name": { + "type": ["null", "string"] + }, + "label": { + "type": ["null", "string"] + }, + "model": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "options": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "null" + } + ] + }, + "type": { + "type": ["null", "string"] + }, + "ui_writable": { + "type": ["null", "boolean"] + }, + "updated_at": { + "format": "date-time", + "type": ["null", "integer"] + } + }, + "type": "object", + "additionalProperties": false + }, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "company_segments", + "json_schema": { + "properties": { + "created_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "count": { + "type": ["null", "integer"] + }, + "id": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "updated_at": { + "format": "date-time", + "type": ["null", "integer"] + } + }, + "type": "object", + "additionalProperties": false + }, + "supported_sync_modes": ["full_refresh", "incremental"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "conversations", + "json_schema": { + "properties": { + "assignee": { + "properties": { + "id": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "email": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "source": { + "properties": { + "type": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + }, + "delivered_as": { + "type": ["null", "string"] + }, + "subject": { + "type": ["null", "string"] + }, + "body": { + "type": ["null", "string"] + }, + "author": { + "properties": { + "id": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "email": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "attachments": { + "items": { + "properties": {}, + "type": ["null", "object"], + "additionalProperties": true + }, + "type": ["null", "array"] + }, + "url": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "contacts": { + "items": { + "properties": { + "type": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "type": ["null", "array"] + }, + "teammates": { + "properties": { + "admins": { + "items": { + "properties": { + "id": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "type": ["null", "array"] + }, + "type": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "first_contact_reply": { + "properties": { + "type": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + }, + "created_at": { + "format": "date-time", + "type": ["null", "integer"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "priority": { + "type": ["null", "string"] + }, + "conversation_message": { + "properties": { + "attachments": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + }, + "content_type": { + "type": ["null", "string"] + }, + "filesize": { + "type": ["null", "integer"] + }, + "height": { + "type": ["null", "integer"] + }, + "width": { + "type": ["null", "integer"] + } + } + } + }, + { + "type": "null" + } + ] + }, + "author": { + "properties": { + "id": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "email": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "body": { + "type": ["null", "string"] + }, + "delivered_as": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + }, + "subject": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "conversation_rating": { + "properties": { + "created_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "customer": { + "properties": { + "id": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "rating": { + "type": ["null", "integer"] + }, + "remark": { + "type": ["null", "string"] + }, + "teammate": { + "properties": { + "id": { + "type": ["null", "integer"] + }, + "type": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "created_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "customer_first_reply": { + "properties": { + "created_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "type": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "customers": { + "anyOf": [ + { + "type": "array", + "items": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "id": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + } + } + } + }, + { + "type": "null" + } + ] + }, + "id": { + "type": ["null", "string"] + }, + "open": { + "type": ["null", "boolean"] + }, + "read": { + "type": ["null", "boolean"] + }, + "sent_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "snoozed_until": { + "format": "date-time", + "type": ["null", "integer"] + }, + "sla_applied": { + "properties": { + "sla_name": { + "type": ["null", "string"] + }, + "sla_status": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"] + }, + "state": { + "type": ["null", "string"] + }, + "statistics": { + "properties": { + "type": { + "type": ["null", "string"] + }, + "time_to_assignment": { + "type": ["null", "integer"] + }, + "time_to_admin_reply": { + "type": ["null", "integer"] + }, + "time_to_first_close": { + "type": ["null", "integer"] + }, + "time_to_last_close": { + "type": ["null", "integer"] + }, + "median_time_to_reply": { + "type": ["null", "integer"] + }, + "first_contact_reply_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "first_assignment_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "first_admin_reply_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "first_close_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "last_assignment_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "last_assignment_admin_reply_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "last_contact_reply_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "last_admin_reply_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "last_close_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "last_closed_by_id": { + "type": ["null", "integer"] + }, + "count_reopens": { + "type": ["null", "integer"] + }, + "count_assignments": { + "type": ["null", "integer"] + }, + "count_conversation_parts": { + "type": ["null", "integer"] + } + }, + "type": ["null", "object"] + }, + "tags": { + "items": { + "properties": { + "applied_at": { + "format": "date-time", + "type": ["null", "string"] + }, + "applied_by": { + "properties": { + "id": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "id": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "type": ["null", "array"] + }, + "type": { + "type": ["null", "string"] + }, + "updated_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "user": { + "properties": { + "id": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "waiting_since": { + "format": "date-time", + "type": ["null", "integer"] + } + }, + "type": "object", + "additionalProperties": false + }, + "supported_sync_modes": ["full_refresh", "incremental"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "conversation_parts", + "json_schema": { + "properties": { + "assigned_to": { + "type": ["null", "string"] + }, + "attachments": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + }, + "content_type": { + "type": ["null", "string"] + }, + "filesize": { + "type": ["null", "integer"] + }, + "height": { + "type": ["null", "integer"] + }, + "width": { + "type": ["null", "integer"] + } + } + } + }, + { + "type": "null" + } + ] + }, + "author": { + "properties": { + "id": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "email": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "body": { + "type": ["null", "string"] + }, + "conversation_id": { + "type": ["null", "string"] + }, + "conversation_created_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "conversation_updated_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "conversation_total_parts": { + "type": ["null", "integer"] + }, + "created_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "external_id": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + }, + "notified_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "part_type": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "updated_at": { + "format": "date-time", + "type": ["null", "integer"] + } + }, + "type": "object", + "additionalProperties": false + }, + "supported_sync_modes": ["full_refresh", "incremental"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "contact_attributes", + "json_schema": { + "properties": { + "type": { + "type": ["null", "string"] + }, + "model": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "full_name": { + "type": ["null", "string"] + }, + "label": { + "type": ["null", "string"] + }, + "description": { + "type": ["null", "string"] + }, + "data_type": { + "type": ["null", "string"] + }, + "options": { + "items": { + "type": ["null", "string"] + }, + "type": ["null", "array"] + }, + "api_writable": { + "type": ["null", "boolean"] + }, + "ui_writable": { + "type": ["null", "boolean"] + }, + "custom": { + "type": ["null", "boolean"] + }, + "archived": { + "type": ["null", "boolean"] + }, + "admin_id": { + "type": ["null", "string"] + }, + "created_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "updated_at": { + "format": "date-time", + "type": ["null", "integer"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "contacts", + "json_schema": { + "properties": { + "type": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + }, + "workspace_id": { + "type": ["null", "string"] + }, + "external_id": { + "type": ["null", "string"] + }, + "role": { + "type": ["null", "string"] + }, + "email": { + "type": ["null", "string"] + }, + "phone": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "avatar": { + "type": ["null", "string"] + }, + "owner_id": { + "type": ["null", "integer"] + }, + "social_profiles": { + "properties": { + "type": { + "type": ["null", "string"] + }, + "data": { + "items": { + "properties": { + "type": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "type": ["null", "array"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "has_hard_bounced": { + "type": ["null", "boolean"] + }, + "marked_email_as_spam": { + "type": ["null", "boolean"] + }, + "unsubscribed_from_emails": { + "type": ["null", "boolean"] + }, + "created_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "updated_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "signed_up_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "last_seen_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "last_replied_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "last_contacted_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "last_email_opened_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "last_email_clicked_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "language_override": { + "type": ["null", "string"] + }, + "browser": { + "type": ["null", "string"] + }, + "browser_version": { + "type": ["null", "string"] + }, + "browser_language": { + "type": ["null", "string"] + }, + "os": { + "type": ["null", "string"] + }, + "location": { + "properties": { + "type": { + "type": ["null", "string"] + }, + "country": { + "type": ["null", "string"] + }, + "region": { + "type": ["null", "string"] + }, + "city": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "android_app_name": { + "type": ["null", "string"] + }, + "android_app_version": { + "type": ["null", "string"] + }, + "android_device": { + "type": ["null", "string"] + }, + "android_os_version": { + "type": ["null", "string"] + }, + "android_sdk_version": { + "type": ["null", "string"] + }, + "android_last_seen_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "ios_app_name": { + "type": ["null", "string"] + }, + "ios_app_version": { + "type": ["null", "string"] + }, + "ios_device": { + "type": ["null", "string"] + }, + "ios_os_version": { + "type": ["null", "string"] + }, + "ios_sdk_version": { + "type": ["null", "string"] + }, + "ios_last_seen_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "custom_attributes": { + "properties": {}, + "type": ["null", "object"], + "additionalProperties": true + }, + "tags": { + "properties": { + "type": { + "type": ["null", "string"] + }, + "data": { + "items": { + "properties": { + "type": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "type": ["null", "array"] + }, + "url": { + "type": ["null", "string"] + }, + "total_count": { + "type": ["null", "integer"] + }, + "has_more": { + "type": ["null", "boolean"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "notes": { + "properties": { + "type": { + "type": ["null", "string"] + }, + "data": { + "items": { + "properties": { + "type": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "type": ["null", "array"] + }, + "url": { + "type": ["null", "string"] + }, + "total_count": { + "type": ["null", "integer"] + }, + "has_more": { + "type": ["null", "boolean"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "companies": { + "properties": { + "type": { + "type": ["null", "string"] + }, + "data": { + "items": { + "properties": { + "type": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "type": ["null", "array"] + }, + "url": { + "type": ["null", "string"] + }, + "total_count": { + "type": ["null", "integer"] + }, + "has_more": { + "type": ["null", "boolean"] + } + }, + "type": ["null", "object"], + "additionalProperties": false + } + }, + "type": ["null", "object"], + "additionalProperties": false + }, + "supported_sync_modes": ["full_refresh", "incremental"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "segments", + "json_schema": { + "properties": { + "created_at": { + "format": "date-time", + "type": ["null", "integer"] + }, + "count": { + "type": ["null", "integer"] + }, + "id": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "updated_at": { + "format": "date-time", + "type": ["null", "integer"] + } + }, + "type": "object", + "additionalProperties": false + }, + "supported_sync_modes": ["full_refresh", "incremental"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "tags", + "json_schema": { + "properties": { + "id": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + } + }, + "type": "object", + "additionalProperties": false + }, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "teams", + "json_schema": { + "properties": { + "admin_ids": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "integer" + } + }, + { + "type": "null" + } + ] + }, + "id": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + } + }, + "type": "object", + "additionalProperties": false + }, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "append" + } + ] +} diff --git a/airbyte-integrations/connectors/source-intercom/integration_tests/invalid_config.json b/airbyte-integrations/connectors/source-intercom/integration_tests/invalid_config.json new file mode 100644 index 000000000000..ab7c557e58f9 --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/integration_tests/invalid_config.json @@ -0,0 +1,4 @@ +{ + "start_date": "2021-11-22T20:32:05Z", + "access_token": "invalid_access_token" +} diff --git a/airbyte-integrations/connectors/source-intercom/integration_tests/sample_config.json b/airbyte-integrations/connectors/source-intercom/integration_tests/sample_config.json new file mode 100644 index 000000000000..80049770c24b --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/integration_tests/sample_config.json @@ -0,0 +1,4 @@ +{ + "start_date": "2020-11-22T20:32:05Z", + "access_token": "access_token" +} diff --git a/airbyte-integrations/connectors/source-intercom/integration_tests/sample_state.json b/airbyte-integrations/connectors/source-intercom/integration_tests/sample_state.json new file mode 100644 index 000000000000..e3e8395eaea0 --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/integration_tests/sample_state.json @@ -0,0 +1,20 @@ +{ + "companies": { + "updated_at": "2019-07-12T11:22:46+00:00" + }, + "company_segments": { + "updated_at": "2019-07-12T11:22:46+00:00" + }, + "conversations": { + "updated_at": "2019-07-12T11:22:46+00:00" + }, + "conversation_parts": { + "updated_at": "2019-07-12T11:22:46+00:00" + }, + "contacts": { + "updated_at": "2019-07-12T11:22:46+00:00" + }, + "segments": { + "updated_at": "2019-07-12T11:22:46+00:00" + } +} diff --git a/airbyte-integrations/connectors/source-intercom/main.py b/airbyte-integrations/connectors/source-intercom/main.py new file mode 100644 index 000000000000..1c18fbae560d --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/main.py @@ -0,0 +1,33 @@ +# +# 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 sys + +from airbyte_cdk.entrypoint import launch +from source_intercom import SourceIntercom + +if __name__ == "__main__": + source = SourceIntercom() + launch(source, sys.argv[1:]) diff --git a/airbyte-integrations/connectors/source-intercom/requirements.txt b/airbyte-integrations/connectors/source-intercom/requirements.txt new file mode 100644 index 000000000000..0411042aa091 --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/requirements.txt @@ -0,0 +1,2 @@ +-e ../../bases/source-acceptance-test +-e . diff --git a/airbyte-integrations/connectors/source-intercom/setup.py b/airbyte-integrations/connectors/source-intercom/setup.py new file mode 100644 index 000000000000..a5107673bff9 --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/setup.py @@ -0,0 +1,48 @@ +# +# MIT License +# +# Copyright (c) 2020 Airbyte +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + + +from setuptools import find_packages, setup + +MAIN_REQUIREMENTS = [ + "airbyte-cdk", +] + +TEST_REQUIREMENTS = [ + "pytest~=6.1", + "source-acceptance-test", +] + +setup( + name="source_intercom", + description="Source implementation for Intercom.", + author="Airbyte", + author_email="contact@airbyte.io", + packages=find_packages(), + install_requires=MAIN_REQUIREMENTS, + package_data={"": ["*.json", "schemas/*.json", "schemas/shared/*.json"]}, + extras_require={ + "tests": TEST_REQUIREMENTS, + }, +) diff --git a/airbyte-integrations/connectors/source-intercom/source_intercom/__init__.py b/airbyte-integrations/connectors/source-intercom/source_intercom/__init__.py new file mode 100644 index 000000000000..9a3ebdd08f65 --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/source_intercom/__init__.py @@ -0,0 +1,27 @@ +""" +MIT License + +Copyright (c) 2020 Airbyte + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from .source import SourceIntercom + +__all__ = ["SourceIntercom"] diff --git a/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/admins.json b/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/admins.json new file mode 100644 index 000000000000..8ca4c3791821 --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/admins.json @@ -0,0 +1,65 @@ +{ + "type": "object", + "additionalProperties": false, + "properties": { + "admin_ids": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "integer" + } + }, + { + "type": "null" + } + ] + }, + "avatar": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "image_url": { + "type": ["null", "string"] + } + } + }, + "away_mode_enabled": { + "type": ["null", "boolean"] + }, + "away_mode_reassign": { + "type": ["null", "boolean"] + }, + "email": { + "type": ["null", "string"] + }, + "has_inbox_seat": { + "type": ["null", "boolean"] + }, + "id": { + "type": ["null", "string"] + }, + "job_title": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "team_ids": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "integer" + } + }, + { + "type": "null" + } + ] + }, + "type": { + "type": ["null", "string"] + } + } +} diff --git a/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/companies.json b/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/companies.json new file mode 100644 index 000000000000..bb23f31f35b9 --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/companies.json @@ -0,0 +1,110 @@ +{ + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": ["null", "string"] + }, + "company_id": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + }, + "app_id": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "created_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "monthly_spend": { + "type": ["null", "number"], + "multipleOf": 1e-8 + }, + "session_count": { + "type": ["null", "integer"] + }, + "user_count": { + "type": ["null", "integer"] + }, + "size": { + "type": ["null", "integer"] + }, + "tags": { + "anyOf": [ + { + "type": ["null", "object"], + "items": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "id": { + "type": ["null", "string"] + } + } + } + }, + { + "type": "null" + } + ] + }, + + "segments": { + "anyOf": [ + { + "type": ["null", "object"], + "items": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "id": { + "type": ["null", "string"] + } + } + } + }, + { + "type": "null" + } + ] + }, + "plan": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "id": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + } + } + }, + "custom_attributes": { + "type": ["null", "object"], + "additionalProperties": true + }, + "industry": { + "type": ["null", "string"] + }, + "remote_created_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "website": { + "type": ["null", "string"] + } + } +} diff --git a/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/company_attributes.json b/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/company_attributes.json new file mode 100644 index 000000000000..b69b6fbd1471 --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/company_attributes.json @@ -0,0 +1,63 @@ +{ + "type": "object", + "additionalProperties": false, + "properties": { + "admin_id": { + "type": ["null", "string"] + }, + "api_writable": { + "type": ["null", "boolean"] + }, + "archived": { + "type": ["null", "boolean"] + }, + "created_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "custom": { + "type": ["null", "boolean"] + }, + "data_type": { + "type": ["null", "string"] + }, + "description": { + "type": ["null", "string"] + }, + "full_name": { + "type": ["null", "string"] + }, + "label": { + "type": ["null", "string"] + }, + "model": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "options": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "null" + } + ] + }, + "type": { + "type": ["null", "string"] + }, + "ui_writable": { + "type": ["null", "boolean"] + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + } + } +} diff --git a/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/company_segments.json b/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/company_segments.json new file mode 100644 index 000000000000..c20c129a5297 --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/company_segments.json @@ -0,0 +1,29 @@ +{ + "type": "object", + "additionalProperties": false, + "properties": { + "created_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "count": { + "type": ["null", "integer"] + }, + "id": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "person_type": { + "type": ["null", "string"] + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + } + } +} diff --git a/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/contact_attributes.json b/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/contact_attributes.json new file mode 100644 index 000000000000..4babd19e5a3e --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/contact_attributes.json @@ -0,0 +1,56 @@ +{ + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "type": { + "type": ["null", "string"] + }, + "model": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "full_name": { + "type": ["null", "string"] + }, + "label": { + "type": ["null", "string"] + }, + "description": { + "type": ["null", "string"] + }, + "data_type": { + "type": ["null", "string"] + }, + "options": { + "type": ["null", "array"], + "items": { + "type": ["null", "string"] + } + }, + "api_writable": { + "type": ["null", "boolean"] + }, + "ui_writable": { + "type": ["null", "boolean"] + }, + "custom": { + "type": ["null", "boolean"] + }, + "archived": { + "type": ["null", "boolean"] + }, + "admin_id": { + "type": ["null", "string"] + }, + "created_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "updated_at": { + "type": ["null", "integer"], + "format": "date-time" + } + } +} diff --git a/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/contacts.json b/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/contacts.json new file mode 100644 index 000000000000..9c30ab910370 --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/contacts.json @@ -0,0 +1,288 @@ +{ + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "type": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + }, + "workspace_id": { + "type": ["null", "string"] + }, + "external_id": { + "type": ["null", "string"] + }, + "role": { + "type": ["null", "string"] + }, + "email": { + "type": ["null", "string"] + }, + "phone": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "avatar": { + "type": ["null", "string"] + }, + "owner_id": { + "type": ["null", "integer"] + }, + "social_profiles": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "type": { + "type": ["null", "string"] + }, + "data": { + "type": ["null", "array"], + "items": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "type": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + } + } + } + } + } + }, + "has_hard_bounced": { + "type": ["null", "boolean"] + }, + "marked_email_as_spam": { + "type": ["null", "boolean"] + }, + "unsubscribed_from_emails": { + "type": ["null", "boolean"] + }, + "created_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "signed_up_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "last_seen_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "last_replied_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "last_contacted_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "last_email_opened_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "last_email_clicked_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "language_override": { + "type": ["null", "string"] + }, + "browser": { + "type": ["null", "string"] + }, + "browser_version": { + "type": ["null", "string"] + }, + "browser_language": { + "type": ["null", "string"] + }, + "os": { + "type": ["null", "string"] + }, + "location": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "type": { + "type": ["null", "string"] + }, + "country": { + "type": ["null", "string"] + }, + "region": { + "type": ["null", "string"] + }, + "city": { + "type": ["null", "string"] + } + } + }, + "android_app_name": { + "type": ["null", "string"] + }, + "android_app_version": { + "type": ["null", "string"] + }, + "android_device": { + "type": ["null", "string"] + }, + "android_os_version": { + "type": ["null", "string"] + }, + "android_sdk_version": { + "type": ["null", "string"] + }, + "android_last_seen_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "ios_app_name": { + "type": ["null", "string"] + }, + "ios_app_version": { + "type": ["null", "string"] + }, + "ios_device": { + "type": ["null", "string"] + }, + "ios_os_version": { + "type": ["null", "string"] + }, + "ios_sdk_version": { + "type": ["null", "string"] + }, + "ios_last_seen_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "custom_attributes": { + "type": ["null", "object"], + "additionalProperties": true, + "properties": {} + }, + "tags": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "type": { + "type": ["null", "string"] + }, + "data": { + "type": ["null", "array"], + "items": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "type": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + } + } + } + }, + "url": { + "type": ["null", "string"] + }, + "total_count": { + "type": ["null", "integer"] + }, + "has_more": { + "type": ["null", "boolean"] + } + } + }, + "notes": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "type": { + "type": ["null", "string"] + }, + "data": { + "type": ["null", "array"], + "items": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "type": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + } + } + } + }, + "url": { + "type": ["null", "string"] + }, + "total_count": { + "type": ["null", "integer"] + }, + "has_more": { + "type": ["null", "boolean"] + } + } + }, + "companies": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "type": { + "type": ["null", "string"] + }, + "data": { + "type": ["null", "array"], + "items": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "type": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + } + } + } + }, + "url": { + "type": ["null", "string"] + }, + "total_count": { + "type": ["null", "integer"] + }, + "has_more": { + "type": ["null", "boolean"] + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/conversation_parts.json b/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/conversation_parts.json new file mode 100644 index 000000000000..76d51823b164 --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/conversation_parts.json @@ -0,0 +1,127 @@ +{ + "type": "object", + "additionalProperties": false, + "properties": { + "assigned_to": { + "anyOf": [ + { + "type": ["null", "object"], + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": ["null", "string"] + } + }, + "id": { + "type": ["null", "string"] + } + } + }, + { + "type": "null" + } + ] + }, + "attachments": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + }, + "content_type": { + "type": ["null", "string"] + }, + "filesize": { + "type": ["null", "integer"] + }, + "height": { + "type": ["null", "integer"] + }, + "width": { + "type": ["null", "integer"] + } + } + } + }, + { + "type": "null" + } + ] + }, + "author": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "id": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "email": { + "type": ["null", "string"] + } + } + }, + "body": { + "type": ["null", "string"] + }, + "conversation_id": { + "type": ["null", "string"] + }, + "conversation_created_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "conversation_updated_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "conversation_total_parts": { + "type": ["null", "integer"] + }, + "created_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "external_id": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + }, + "notified_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "part_type": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "redacted": { + "type": ["null", "boolean"] + } + } +} diff --git a/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/conversations.json b/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/conversations.json new file mode 100644 index 000000000000..b48e7a2d40e2 --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/conversations.json @@ -0,0 +1,467 @@ +{ + "type": "object", + "additionalProperties": false, + "properties": { + "assignee": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "id": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "email": { + "type": ["null", "string"] + } + } + }, + "source": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "type": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + }, + "redacted": { + "type": ["null", "boolean"] + }, + "delivered_as": { + "type": ["null", "string"] + }, + "subject": { + "type": ["null", "string"] + }, + "body": { + "type": ["null", "string"] + }, + "author": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "id": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "email": { + "type": ["null", "string"] + } + } + }, + "attachments": { + "type": ["null", "array"], + "items": { + "type": ["null", "object"], + "additionalProperties": true, + "properties": {} + } + }, + "url": { + "type": ["null", "string"] + } + } + }, + "contacts": { + "type": ["null", "object"], + "items": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "type": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + } + } + } + }, + "teammates": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "admins": { + "type": ["null", "array"], + "items": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "id": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + } + } + } + }, + "type": { + "type": ["null", "string"] + } + } + }, + "first_contact_reply": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "type": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + }, + "created_at": { + "type": ["null", "integer"], + "format": "date-time" + } + } + }, + "priority": { + "type": ["null", "string"] + }, + "conversation_message": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "attachments": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + }, + "content_type": { + "type": ["null", "string"] + }, + "filesize": { + "type": ["null", "integer"] + }, + "height": { + "type": ["null", "integer"] + }, + "width": { + "type": ["null", "integer"] + } + } + } + }, + { + "type": "null" + } + ] + }, + "author": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "id": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "email": { + "type": ["null", "string"] + } + } + }, + "body": { + "type": ["null", "string"] + }, + "delivered_as": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + }, + "subject": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + } + } + }, + "conversation_rating": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "created_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "customer": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "id": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + } + } + }, + "rating": { + "type": ["null", "integer"] + }, + "remark": { + "type": ["null", "string"] + }, + "teammate": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "id": { + "type": ["null", "integer"] + }, + "type": { + "type": ["null", "string"] + } + } + } + } + }, + "created_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "customer_first_reply": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "created_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "type": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + } + } + }, + "customers": { + "anyOf": [ + { + "type": "array", + "items": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "id": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + } + } + } + }, + { + "type": "null" + } + ] + }, + "id": { + "type": ["null", "string"] + }, + "open": { + "type": ["null", "boolean"] + }, + "read": { + "type": ["null", "boolean"] + }, + "sent_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "snoozed_until": { + "type": ["null", "integer"], + "format": "date-time" + }, + "sla_applied": { + "type": ["null", "object"], + "properties": { + "sla_name": { + "type": ["null", "string"] + }, + "sla_status": { + "type": ["null", "string"] + } + } + }, + "state": { + "type": ["null", "string"] + }, + "statistics": { + "type": ["null", "object"], + "properties": { + "type": { + "type": ["null", "string"] + }, + "time_to_assignment": { + "type": ["null", "integer"] + }, + "time_to_admin_reply": { + "type": ["null", "integer"] + }, + "time_to_first_close": { + "type": ["null", "integer"] + }, + "time_to_last_close": { + "type": ["null", "integer"] + }, + "median_time_to_reply": { + "type": ["null", "integer"] + }, + "first_contact_reply_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "first_assignment_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "first_admin_reply_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "first_close_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "last_assignment_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "last_assignment_admin_reply_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "last_contact_reply_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "last_admin_reply_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "last_close_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "last_closed_by_id": { + "type": ["null", "integer"] + }, + "count_reopens": { + "type": ["null", "integer"] + }, + "count_assignments": { + "type": ["null", "integer"] + }, + "count_conversation_parts": { + "type": ["null", "integer"] + } + } + }, + "tags": { + "type": ["null", "object"], + "items": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "applied_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "applied_by": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "id": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + } + } + }, + "id": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + } + } + } + }, + "type": { + "type": ["null", "string"] + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "user": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "id": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + } + } + }, + "waiting_since": { + "type": ["null", "integer"], + "format": "date-time" + }, + "admin_assignee_id": { + "type": ["null", "integer"] + }, + "title": { + "type": ["null", "string"] + }, + "team_assignee_id": { + "type": ["null", "integer"] + }, + "redacted": { + "type": ["null", "boolean"] + } + } +} diff --git a/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/segments.json b/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/segments.json new file mode 100644 index 000000000000..c20c129a5297 --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/segments.json @@ -0,0 +1,29 @@ +{ + "type": "object", + "additionalProperties": false, + "properties": { + "created_at": { + "type": ["null", "integer"], + "format": "date-time" + }, + "count": { + "type": ["null", "integer"] + }, + "id": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "person_type": { + "type": ["null", "string"] + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + } + } +} diff --git a/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/tags.json b/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/tags.json new file mode 100644 index 000000000000..ab2658ecee5e --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/tags.json @@ -0,0 +1,15 @@ +{ + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + } + } +} diff --git a/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/teams.json b/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/teams.json new file mode 100644 index 000000000000..9783dcb3da9c --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/source_intercom/schemas/teams.json @@ -0,0 +1,28 @@ +{ + "type": "object", + "additionalProperties": false, + "properties": { + "admin_ids": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "integer" + } + }, + { + "type": "null" + } + ] + }, + "id": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + } + } +} diff --git a/airbyte-integrations/connectors/source-intercom/source_intercom/source.py b/airbyte-integrations/connectors/source-intercom/source_intercom/source.py new file mode 100644 index 000000000000..f3d65f4be67e --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/source_intercom/source.py @@ -0,0 +1,342 @@ +# +# 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 time +from abc import ABC +from datetime import datetime +from typing import Any, Iterable, List, Mapping, MutableMapping, Optional, Tuple + +import requests +from airbyte_cdk.logger import AirbyteLogger +from airbyte_cdk.sources import AbstractSource +from airbyte_cdk.sources.streams import Stream +from airbyte_cdk.sources.streams.http import HttpStream +from airbyte_cdk.sources.streams.http.auth import HttpAuthenticator, TokenAuthenticator + + +class IntercomStream(HttpStream, ABC): + url_base = "https://api.intercom.io/" + + # https://developers.intercom.com/intercom-api-reference/reference#rate-limiting + queries_per_hour = 1000 # 1000 queries per hour == 1 req in 3,6 secs + + primary_key = "id" + data_fields = ["data"] + + def __init__( + self, + authenticator: HttpAuthenticator, + start_date: str = None, + **kwargs, + ): + self.start_date = start_date + + super().__init__(authenticator=authenticator) + + def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]: + """ + Abstract method of HttpStream - should be overwritten. + Returning None means there are no more pages to read in response. + """ + + next_page = response.json().get("pages", {}).get("next") + + if next_page: + return {"starting_after": next_page["starting_after"]} + else: + return None + + def request_params(self, next_page_token: Mapping[str, Any] = None, **kwargs) -> MutableMapping[str, Any]: + params = {} + if next_page_token: + params.update(**next_page_token) + return params + + def request_headers(self, **kwargs) -> Mapping[str, Any]: + return {"Accept": "application/json"} + + def read_records(self, *args, **kwargs) -> Iterable[Mapping[str, Any]]: + try: + yield from super().read_records(*args, **kwargs) + except requests.exceptions.HTTPError as e: + error_message = e.response.text + if error_message: + self.logger.error(f"Stream {self.name}: {e.response.status_code} " f"{e.response.reason} - {error_message}") + raise e + + def get_data(self, response: requests.Response) -> List: + data = response.json() + + for data_field in self.data_fields: + if data and isinstance(data, dict): + data = data.get(data_field, []) + + if isinstance(data, list): + data = data + elif isinstance(data, dict): + data = [data] + + return data + + def parse_response(self, response: requests.Response, stream_state: Mapping[str, Any], **kwargs) -> Iterable[Mapping]: + data = self.get_data(response) + + for record in data: + yield record + + # wait for 3,6 seconds according to API limit + time.sleep(3600 / self.queries_per_hour) + + +class IncrementalIntercomStream(IntercomStream, ABC): + cursor_field = "updated_at" + + def filter_by_state(self, stream_state: Mapping[str, Any] = None, record: Mapping[str, Any] = None) -> Iterable: + """ + Endpoint does not provide query filtering params, but they provide us + updated_at field in most cases, so we used that as incremental filtering + during the slicing. + """ + + if not stream_state or record[self.cursor_field] >= stream_state.get(self.cursor_field): + yield record + + def parse_response(self, response: requests.Response, stream_state: Mapping[str, Any], **kwargs) -> Iterable[Mapping]: + record = super().parse_response(response, stream_state, **kwargs) + + for record in record: + updated_at = record.get(self.cursor_field) + + if updated_at: + record[self.cursor_field] = datetime.fromtimestamp( + record[self.cursor_field] + ).isoformat() # convert timestamp to datetime string + + yield from self.filter_by_state(stream_state=stream_state, record=record) + + def get_updated_state(self, current_stream_state: MutableMapping[str, Any], latest_record: Mapping[str, Any]) -> Mapping[str, any]: + """ + This method is called once for each record returned from the API to + compare the cursor field value in that record with the current state + we then return an updated state object. If this is the first time we + run a sync or no state was passed, current_stream_state will be None. + """ + + current_stream_state = current_stream_state or {} + + current_stream_state_date = current_stream_state.get(self.cursor_field, self.start_date) + latest_record_date = latest_record.get(self.cursor_field, self.start_date) + + return {self.cursor_field: max(current_stream_state_date, latest_record_date)} + + +class ChildStreamMixin: + parent_stream_class: Optional[IntercomStream] = None + + def stream_slices(self, sync_mode, **kwargs) -> Iterable[Optional[Mapping[str, any]]]: + for item in self.parent_stream_class(authenticator=self.authenticator, start_date=self.start_date).read_records( + sync_mode=sync_mode + ): + yield {"id": item["id"]} + + yield from [] + + +class Admins(IntercomStream): + """Return list of all admins. + API Docs: https://developers.intercom.com/intercom-api-reference/reference#list-admins + Endpoint: https://api.intercom.io/admins + """ + + data_fields = ["admins"] + + def path(self, **kwargs) -> str: + return "admins" + + +class Companies(IncrementalIntercomStream): + """Return list of all companies. + API Docs: https://developers.intercom.com/intercom-api-reference/reference#iterating-over-all-companies + Endpoint: https://api.intercom.io/companies/scroll + """ + + def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]: + """For reset scroll needs to iterate pages untill the last. + Another way need wait 1 min for the scroll to expire to get a new list for companies segments.""" + + data = response.json().get("data") + + if data: + return {"scroll_param": response.json()["scroll_param"]} + else: + return None + + def path(self, **kwargs) -> str: + return "companies/scroll" + + +class CompanySegments(ChildStreamMixin, IncrementalIntercomStream): + """Return list of all company segments. + API Docs: https://developers.intercom.com/intercom-api-reference/reference#list-attached-segments-1 + Endpoint: https://api.intercom.io/companies//segments + """ + + parent_stream_class = Companies + + def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: + return f"/companies/{stream_slice['id']}/segments" + + +class Conversations(IncrementalIntercomStream): + """Return list of all conversations. + API Docs: https://developers.intercom.com/intercom-api-reference/reference#list-conversations + Endpoint: https://api.intercom.io/conversations + """ + + data_fields = ["conversations"] + + def path(self, **kwargs) -> str: + return "conversations" + + +class ConversationParts(ChildStreamMixin, IncrementalIntercomStream): + """Return list of all conversation parts. + API Docs: https://developers.intercom.com/intercom-api-reference/reference#retrieve-a-conversation + Endpoint: https://api.intercom.io/conversations/ + """ + + data_fields = ["conversation_parts", "conversation_parts"] + parent_stream_class = Conversations + + def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: + return f"/conversations/{stream_slice['id']}" + + +class Segments(IncrementalIntercomStream): + """Return list of all segments. + API Docs: https://developers.intercom.com/intercom-api-reference/reference#list-segments + Endpoint: https://api.intercom.io/segments + """ + + data_fields = ["segments"] + + def path(self, **kwargs) -> str: + return "segments" + + +class Contacts(IncrementalIntercomStream): + """Return list of all contacts. + API Docs: https://developers.intercom.com/intercom-api-reference/reference#list-contacts + Endpoint: https://api.intercom.io/contacts + """ + + def path(self, **kwargs) -> str: + return "contacts" + + +class DataAttributes(IntercomStream): + primary_key = "name" + + def path(self, **kwargs) -> str: + return "data_attributes" + + +class CompanyAttributes(DataAttributes): + """Return list of all data attributes belonging to a workspace for companies. + API Docs: https://developers.intercom.com/intercom-api-reference/reference#list-data-attributes + Endpoint: https://api.intercom.io/data_attributes?model=company + """ + + def request_params(self, **kwargs) -> MutableMapping[str, Any]: + return {"model": "company"} + + +class ContactAttributes(DataAttributes): + """Return list of all data attributes belonging to a workspace for contacts. + API Docs: https://developers.intercom.com/intercom-api-reference/reference#list-data-attributes + Endpoint: https://api.intercom.io/data_attributes?model=contact + """ + + def request_params(self, **kwargs) -> MutableMapping[str, Any]: + return {"model": "contact"} + + +class Tags(IntercomStream): + """Return list of all tags. + API Docs: https://developers.intercom.com/intercom-api-reference/reference#list-tags-for-an-app + Endpoint: https://api.intercom.io/tags + """ + + primary_key = "name" + + def path(self, **kwargs) -> str: + return "tags" + + +class Teams(IntercomStream): + """Return list of all teams. + API Docs: https://developers.intercom.com/intercom-api-reference/reference#list-teams + Endpoint: https://api.intercom.io/teams + """ + + primary_key = "name" + data_fields = ["teams"] + + def path(self, **kwargs) -> str: + return "teams" + + +class SourceIntercom(AbstractSource): + """ + Source Intercom fetch data from messaging platform. + """ + + def check_connection(self, logger, config) -> Tuple[bool, any]: + authenticator = TokenAuthenticator(token=config["access_token"]) + try: + url = f"{IntercomStream.url_base}/tags" + auth_headers = {"Accept": "application/json", **authenticator.get_auth_header()} + session = requests.get(url, headers=auth_headers) + session.raise_for_status() + return True, None + except requests.exceptions.RequestException as e: + return False, e + + def streams(self, config: Mapping[str, Any]) -> List[Stream]: + AirbyteLogger().log("INFO", f"Using start_date: {config['start_date']}") + + auth = TokenAuthenticator(token=config["access_token"]) + return [ + Admins(authenticator=auth, **config), + Companies(authenticator=auth, **config), + CompanySegments(authenticator=auth, **config), + Conversations(authenticator=auth, **config), + ConversationParts(authenticator=auth, **config), + Contacts(authenticator=auth, **config), + CompanyAttributes(authenticator=auth, **config), + ContactAttributes(authenticator=auth, **config), + Segments(authenticator=auth, **config), + Tags(authenticator=auth, **config), + Teams(authenticator=auth, **config), + ] diff --git a/airbyte-integrations/connectors/source-intercom/source_intercom/spec.json b/airbyte-integrations/connectors/source-intercom/source_intercom/spec.json new file mode 100644 index 000000000000..a233d03a9084 --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/source_intercom/spec.json @@ -0,0 +1,23 @@ +{ + "documentationUrl": "https://docs.airbyte.io/integrations/sources/intercom", + "connectionSpecification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Source Intercom Spec", + "type": "object", + "required": ["access_token", "start_date"], + "additionalProperties": false, + "properties": { + "access_token": { + "type": "string", + "description": "Intercom Access Token. See the docs for more information on how to obtain this key.", + "airbyte_secret": true + }, + "start_date": { + "type": "string", + "description": "The date from which you'd like to replicate data for Intercom API, in the format YYYY-MM-DDT00:00:00Z. All data generated after this date will be replicated.", + "examples": ["2020-11-16T00:00:00Z"], + "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$" + } + } + } +} diff --git a/airbyte-integrations/connectors/source-intercom/unit_tests/unit_test.py b/airbyte-integrations/connectors/source-intercom/unit_tests/unit_test.py new file mode 100644 index 000000000000..b8a8150b507f --- /dev/null +++ b/airbyte-integrations/connectors/source-intercom/unit_tests/unit_test.py @@ -0,0 +1,27 @@ +# +# 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. +# + + +def test_example_method(): + assert True diff --git a/docs/integrations/sources/intercom.md b/docs/integrations/sources/intercom.md index 653aec89d0a2..59e46043cab2 100644 --- a/docs/integrations/sources/intercom.md +++ b/docs/integrations/sources/intercom.md @@ -4,25 +4,24 @@ The Intercom source supports both Full Refresh and Incremental syncs. You can choose if this connector will copy only the new or updated data, or all rows in the tables and columns you set up for replication, every time a sync is run. -This Intercom source wraps the [Singer Intercom Tap](https://github.com/singer-io/tap-intercom). +This Source Connector is based on a [Airbyte CDK](https://docs.airbyte.io/contributing-to-airbyte/python). ### Output schema Several output streams are available from this source: -* [Admins](https://developers.intercom.com/intercom-api-reference/reference#list-admins) -* [Companies](https://developers.intercom.com/intercom-api-reference/reference#list-companies) -* [Conversations](https://developers.intercom.com/intercom-api-reference/reference#list-conversations) - * [Conversation Parts](https://developers.intercom.com/intercom-api-reference/reference#get-a-single-conversation) -* [Data Attributes](https://developers.intercom.com/intercom-api-reference/reference#data-attributes) - * [Customer Attributes](https://developers.intercom.com/intercom-api-reference/reference#list-customer-data-attributes) - * [Company Attributes](https://developers.intercom.com/intercom-api-reference/reference#list-company-data-attributes) -* [Leads](https://developers.intercom.com/intercom-api-reference/reference#list-leads) -* [Segments](https://developers.intercom.com/intercom-api-reference/reference#list-segments) - * [Company Segments](https://developers.intercom.com/intercom-api-reference/reference#list-segments) -* [Tags](https://developers.intercom.com/intercom-api-reference/reference#list-tags-for-an-app) -* [Teams](https://developers.intercom.com/intercom-api-reference/reference#list-teams) -* [Users](https://developers.intercom.com/intercom-api-reference/reference#list-users) +* [Admins](https://developers.intercom.com/intercom-api-reference/reference#list-admins) \(Full table\) +* [Companies](https://developers.intercom.com/intercom-api-reference/reference#list-companies) \(Incremental\) + * [Company Segments](https://developers.intercom.com/intercom-api-reference/reference#list-attached-segments-1) \(Incremental\) +* [Conversations](https://developers.intercom.com/intercom-api-reference/reference#list-conversations) \(Incremental\) + * [Conversation Parts](https://developers.intercom.com/intercom-api-reference/reference#get-a-single-conversation) \(Incremental\) +* [Data Attributes](https://developers.intercom.com/intercom-api-reference/reference#data-attributes) \(Full table\) + * [Customer Attributes](https://developers.intercom.com/intercom-api-reference/reference#list-customer-data-attributes) \(Full table\) + * [Company Attributes](https://developers.intercom.com/intercom-api-reference/reference#list-company-data-attributes) \(Full table\) +* [Contacts](https://developers.intercom.com/intercom-api-reference/reference#list-contacts) \(Incremental\) +* [Segments](https://developers.intercom.com/intercom-api-reference/reference#list-segments) \(Incremental\) +* [Tags](https://developers.intercom.com/intercom-api-reference/reference#list-tags-for-an-app) \(Full table\) +* [Teams](https://developers.intercom.com/intercom-api-reference/reference#list-teams) \(Full table\) If there are more endpoints you'd like Airbyte to support, please [create an issue.](https://github.com/airbytehq/airbyte/issues/new/choose) diff --git a/tools/bin/ci_credentials.sh b/tools/bin/ci_credentials.sh index 3f4a438281ca..2e2b802fb621 100755 --- a/tools/bin/ci_credentials.sh +++ b/tools/bin/ci_credentials.sh @@ -61,6 +61,7 @@ write_standard_creds source-greenhouse "$GREENHOUSE_TEST_CREDS" write_standard_creds source-harvest "$HARVEST_INTEGRATION_TESTS_CREDS" write_standard_creds source-hubspot "$HUBSPOT_INTEGRATION_TESTS_CREDS" write_standard_creds source-instagram "$INSTAGRAM_INTEGRATION_TESTS_CREDS" +write_standard_creds source-intercom "$INTERCOM_INTEGRATION_TEST_CREDS" write_standard_creds source-intercom-singer "$INTERCOM_INTEGRATION_TEST_CREDS" write_standard_creds source-iterable "$ITERABLE_INTEGRATION_TEST_CREDS" write_standard_creds source-jira "$JIRA_INTEGRATION_TEST_CREDS"