diff --git a/airbyte-integrations/connectors/source-zendesk-talk/acceptance-test-config.yml b/airbyte-integrations/connectors/source-zendesk-talk/acceptance-test-config.yml index 6600e4095132..aa7110c4b54e 100644 --- a/airbyte-integrations/connectors/source-zendesk-talk/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-zendesk-talk/acceptance-test-config.yml @@ -12,7 +12,7 @@ acceptance_tests: - config_path: "secrets/config.json" status: "succeed" - config_path: "integration_tests/invalid_config.json" - status: "exception" + status: "failed" - config_path: "secrets/config_old.json" status: "succeed" discovery: diff --git a/airbyte-integrations/connectors/source-zendesk-talk/metadata.yaml b/airbyte-integrations/connectors/source-zendesk-talk/metadata.yaml index eae520a246eb..2e7708f46517 100644 --- a/airbyte-integrations/connectors/source-zendesk-talk/metadata.yaml +++ b/airbyte-integrations/connectors/source-zendesk-talk/metadata.yaml @@ -11,7 +11,7 @@ data: connectorSubtype: api connectorType: source definitionId: c8630570-086d-4a40-99ae-ea5b18673071 - dockerImageTag: 0.2.1 + dockerImageTag: 1.0.0 dockerRepository: airbyte/source-zendesk-talk documentationUrl: https://docs.airbyte.com/integrations/sources/zendesk-talk githubIssueLabel: source-zendesk-talk @@ -30,7 +30,16 @@ data: enabled: true releaseStage: generally_available supportLevel: certified + releases: + breakingChanges: + 1.0.0: + upgradeDeadline: "2024-05-31" + message: >- + The source Zendesk Talk connector is being migrated from the Python CDK to our declarative low-code CDK. + Due to changes to the incremental stream state message format and the removal of a nonexistent field from + the ivrs stream schema, this migration constitutes a breaking change. After updating, please reset your source + before resuming syncs. For more information, see our migration documentation for source Zendesk Talk. tags: - language:python - - cdk:python + - cdk:low-code metadataSpecVersion: "1.0" diff --git a/airbyte-integrations/connectors/source-zendesk-talk/poetry.lock b/airbyte-integrations/connectors/source-zendesk-talk/poetry.lock index 778d4312bf60..37bba43d7e30 100644 --- a/airbyte-integrations/connectors/source-zendesk-talk/poetry.lock +++ b/airbyte-integrations/connectors/source-zendesk-talk/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "airbyte-cdk" @@ -288,13 +288,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, ] [package.extras] @@ -312,13 +312,13 @@ files = [ [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] @@ -348,13 +348,13 @@ six = "*" [[package]] name = "jinja2" -version = "3.1.3" +version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] @@ -511,28 +511,29 @@ pytzdata = ">=2020.1" [[package]] name = "platformdirs" -version = "4.2.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.2.1" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, + {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -552,47 +553,47 @@ files = [ [[package]] name = "pydantic" -version = "1.10.14" +version = "1.10.15" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f4fcec873f90537c382840f330b90f4715eebc2bc9925f04cb92de593eae054"}, - {file = "pydantic-1.10.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e3a76f571970fcd3c43ad982daf936ae39b3e90b8a2e96c04113a369869dc87"}, - {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d886bd3c3fbeaa963692ef6b643159ccb4b4cefaf7ff1617720cbead04fd1d"}, - {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:798a3d05ee3b71967844a1164fd5bdb8c22c6d674f26274e78b9f29d81770c4e"}, - {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:23d47a4b57a38e8652bcab15a658fdb13c785b9ce217cc3a729504ab4e1d6bc9"}, - {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f9f674b5c3bebc2eba401de64f29948ae1e646ba2735f884d1594c5f675d6f2a"}, - {file = "pydantic-1.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:24a7679fab2e0eeedb5a8924fc4a694b3bcaac7d305aeeac72dd7d4e05ecbebf"}, - {file = "pydantic-1.10.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d578ac4bf7fdf10ce14caba6f734c178379bd35c486c6deb6f49006e1ba78a7"}, - {file = "pydantic-1.10.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa7790e94c60f809c95602a26d906eba01a0abee9cc24150e4ce2189352deb1b"}, - {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad4e10efa5474ed1a611b6d7f0d130f4aafadceb73c11d9e72823e8f508e663"}, - {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245f4f61f467cb3dfeced2b119afef3db386aec3d24a22a1de08c65038b255f"}, - {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:21efacc678a11114c765eb52ec0db62edffa89e9a562a94cbf8fa10b5db5c046"}, - {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:412ab4a3f6dbd2bf18aefa9f79c7cca23744846b31f1d6555c2ee2b05a2e14ca"}, - {file = "pydantic-1.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:e897c9f35281f7889873a3e6d6b69aa1447ceb024e8495a5f0d02ecd17742a7f"}, - {file = "pydantic-1.10.14-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d604be0f0b44d473e54fdcb12302495fe0467c56509a2f80483476f3ba92b33c"}, - {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42c7d17706911199798d4c464b352e640cab4351efe69c2267823d619a937e5"}, - {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:596f12a1085e38dbda5cbb874d0973303e34227b400b6414782bf205cc14940c"}, - {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bfb113860e9288d0886e3b9e49d9cf4a9d48b441f52ded7d96db7819028514cc"}, - {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bc3ed06ab13660b565eed80887fcfbc0070f0aa0691fbb351657041d3e874efe"}, - {file = "pydantic-1.10.14-cp37-cp37m-win_amd64.whl", hash = "sha256:ad8c2bc677ae5f6dbd3cf92f2c7dc613507eafe8f71719727cbc0a7dec9a8c01"}, - {file = "pydantic-1.10.14-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c37c28449752bb1f47975d22ef2882d70513c546f8f37201e0fec3a97b816eee"}, - {file = "pydantic-1.10.14-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49a46a0994dd551ec051986806122767cf144b9702e31d47f6d493c336462597"}, - {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53e3819bd20a42470d6dd0fe7fc1c121c92247bca104ce608e609b59bc7a77ee"}, - {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbb503bbbbab0c588ed3cd21975a1d0d4163b87e360fec17a792f7d8c4ff29f"}, - {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:336709883c15c050b9c55a63d6c7ff09be883dbc17805d2b063395dd9d9d0022"}, - {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4ae57b4d8e3312d486e2498d42aed3ece7b51848336964e43abbf9671584e67f"}, - {file = "pydantic-1.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:dba49d52500c35cfec0b28aa8b3ea5c37c9df183ffc7210b10ff2a415c125c4a"}, - {file = "pydantic-1.10.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c66609e138c31cba607d8e2a7b6a5dc38979a06c900815495b2d90ce6ded35b4"}, - {file = "pydantic-1.10.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d986e115e0b39604b9eee3507987368ff8148222da213cd38c359f6f57b3b347"}, - {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:646b2b12df4295b4c3148850c85bff29ef6d0d9621a8d091e98094871a62e5c7"}, - {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282613a5969c47c83a8710cc8bfd1e70c9223feb76566f74683af889faadc0ea"}, - {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:466669501d08ad8eb3c4fecd991c5e793c4e0bbd62299d05111d4f827cded64f"}, - {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:13e86a19dca96373dcf3190fcb8797d40a6f12f154a244a8d1e8e03b8f280593"}, - {file = "pydantic-1.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:08b6ec0917c30861e3fe71a93be1648a2aa4f62f866142ba21670b24444d7fd8"}, - {file = "pydantic-1.10.14-py3-none-any.whl", hash = "sha256:8ee853cd12ac2ddbf0ecbac1c289f95882b2d4482258048079d13be700aa114c"}, - {file = "pydantic-1.10.14.tar.gz", hash = "sha256:46f17b832fe27de7850896f3afee50ea682220dd218f7e9c88d436788419dca6"}, + {file = "pydantic-1.10.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:22ed12ee588b1df028a2aa5d66f07bf8f8b4c8579c2e96d5a9c1f96b77f3bb55"}, + {file = "pydantic-1.10.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:75279d3cac98186b6ebc2597b06bcbc7244744f6b0b44a23e4ef01e5683cc0d2"}, + {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50f1666a9940d3d68683c9d96e39640f709d7a72ff8702987dab1761036206bb"}, + {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82790d4753ee5d00739d6cb5cf56bceb186d9d6ce134aca3ba7befb1eedbc2c8"}, + {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d207d5b87f6cbefbdb1198154292faee8017d7495a54ae58db06762004500d00"}, + {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e49db944fad339b2ccb80128ffd3f8af076f9f287197a480bf1e4ca053a866f0"}, + {file = "pydantic-1.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:d3b5c4cbd0c9cb61bbbb19ce335e1f8ab87a811f6d589ed52b0254cf585d709c"}, + {file = "pydantic-1.10.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c3d5731a120752248844676bf92f25a12f6e45425e63ce22e0849297a093b5b0"}, + {file = "pydantic-1.10.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c365ad9c394f9eeffcb30a82f4246c0006417f03a7c0f8315d6211f25f7cb654"}, + {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3287e1614393119c67bd4404f46e33ae3be3ed4cd10360b48d0a4459f420c6a3"}, + {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be51dd2c8596b25fe43c0a4a59c2bee4f18d88efb8031188f9e7ddc6b469cf44"}, + {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6a51a1dd4aa7b3f1317f65493a182d3cff708385327c1c82c81e4a9d6d65b2e4"}, + {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4e316e54b5775d1eb59187f9290aeb38acf620e10f7fd2f776d97bb788199e53"}, + {file = "pydantic-1.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:0d142fa1b8f2f0ae11ddd5e3e317dcac060b951d605fda26ca9b234b92214986"}, + {file = "pydantic-1.10.15-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7ea210336b891f5ea334f8fc9f8f862b87acd5d4a0cbc9e3e208e7aa1775dabf"}, + {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3453685ccd7140715e05f2193d64030101eaad26076fad4e246c1cc97e1bb30d"}, + {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bea1f03b8d4e8e86702c918ccfd5d947ac268f0f0cc6ed71782e4b09353b26f"}, + {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:005655cabc29081de8243126e036f2065bd7ea5b9dff95fde6d2c642d39755de"}, + {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:af9850d98fc21e5bc24ea9e35dd80a29faf6462c608728a110c0a30b595e58b7"}, + {file = "pydantic-1.10.15-cp37-cp37m-win_amd64.whl", hash = "sha256:d31ee5b14a82c9afe2bd26aaa405293d4237d0591527d9129ce36e58f19f95c1"}, + {file = "pydantic-1.10.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5e09c19df304b8123938dc3c53d3d3be6ec74b9d7d0d80f4f4b5432ae16c2022"}, + {file = "pydantic-1.10.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7ac9237cd62947db00a0d16acf2f3e00d1ae9d3bd602b9c415f93e7a9fc10528"}, + {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:584f2d4c98ffec420e02305cf675857bae03c9d617fcfdc34946b1160213a948"}, + {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbc6989fad0c030bd70a0b6f626f98a862224bc2b1e36bfc531ea2facc0a340c"}, + {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d573082c6ef99336f2cb5b667b781d2f776d4af311574fb53d908517ba523c22"}, + {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6bd7030c9abc80134087d8b6e7aa957e43d35714daa116aced57269a445b8f7b"}, + {file = "pydantic-1.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:3350f527bb04138f8aff932dc828f154847fbdc7a1a44c240fbfff1b57f49a12"}, + {file = "pydantic-1.10.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:51d405b42f1b86703555797270e4970a9f9bd7953f3990142e69d1037f9d9e51"}, + {file = "pydantic-1.10.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a980a77c52723b0dc56640ced396b73a024d4b74f02bcb2d21dbbac1debbe9d0"}, + {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67f1a1fb467d3f49e1708a3f632b11c69fccb4e748a325d5a491ddc7b5d22383"}, + {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:676ed48f2c5bbad835f1a8ed8a6d44c1cd5a21121116d2ac40bd1cd3619746ed"}, + {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:92229f73400b80c13afcd050687f4d7e88de9234d74b27e6728aa689abcf58cc"}, + {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2746189100c646682eff0bce95efa7d2e203420d8e1c613dc0c6b4c1d9c1fde4"}, + {file = "pydantic-1.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:394f08750bd8eaad714718812e7fab615f873b3cdd0b9d84e76e51ef3b50b6b7"}, + {file = "pydantic-1.10.15-py3-none-any.whl", hash = "sha256:28e552a060ba2740d0d2aabe35162652c1459a0b9069fe0db7f4ee0e18e74d58"}, + {file = "pydantic-1.10.15.tar.gz", hash = "sha256:ca832e124eda231a60a041da4f013e3ff24949d94a01154b137fc2f2a43c3ffb"}, ] [package.dependencies] @@ -837,13 +838,13 @@ yaml = ["pyyaml (>=6.0.1)"] [[package]] name = "requests-mock" -version = "1.12.0" +version = "1.12.1" description = "Mock out responses from the requests package" optional = false -python-versions = "*" +python-versions = ">=3.5" files = [ - {file = "requests-mock-1.12.0.tar.gz", hash = "sha256:4e34f2a2752f0b78397fb414526605d95fcdeab021ac1f26d18960e7eb41f6a8"}, - {file = "requests_mock-1.12.0-py2.py3-none-any.whl", hash = "sha256:4f6fdf956de568e0bac99eee4ad96b391c602e614cc0ad33e7f5c72edd699e70"}, + {file = "requests-mock-1.12.1.tar.gz", hash = "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401"}, + {file = "requests_mock-1.12.1-py2.py3-none-any.whl", hash = "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563"}, ] [package.dependencies] @@ -854,18 +855,18 @@ fixture = ["fixtures"] [[package]] name = "setuptools" -version = "69.2.0" +version = "69.5.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"}, - {file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"}, + {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, + {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] @@ -892,13 +893,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.10.0" +version = "4.11.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, - {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, ] [[package]] diff --git a/airbyte-integrations/connectors/source-zendesk-talk/pyproject.toml b/airbyte-integrations/connectors/source-zendesk-talk/pyproject.toml index e9f23c40425f..65b59ad8c9b2 100644 --- a/airbyte-integrations/connectors/source-zendesk-talk/pyproject.toml +++ b/airbyte-integrations/connectors/source-zendesk-talk/pyproject.toml @@ -3,7 +3,7 @@ requires = [ "poetry-core>=1.0.0",] build-backend = "poetry.core.masonry.api" [tool.poetry] -version = "0.2.1" +version = "1.0.0" name = "source-zendesk-talk" description = "Source implementation for Zendesk Talk." authors = [ "Airbyte ",] diff --git a/airbyte-integrations/connectors/source-zendesk-talk/source_zendesk_talk/components.py b/airbyte-integrations/connectors/source-zendesk-talk/source_zendesk_talk/components.py new file mode 100644 index 000000000000..d8030841afe2 --- /dev/null +++ b/airbyte-integrations/connectors/source-zendesk-talk/source_zendesk_talk/components.py @@ -0,0 +1,52 @@ +# Copyright (c) 2023 Airbyte, Inc., all rights reserved. + +from dataclasses import dataclass +from typing import Any, List, Mapping + +import requests +from airbyte_cdk.sources.declarative.auth.declarative_authenticator import DeclarativeAuthenticator +from airbyte_cdk.sources.declarative.auth.token import BasicHttpAuthenticator, BearerAuthenticator +from airbyte_cdk.sources.declarative.extractors.record_extractor import RecordExtractor +from airbyte_cdk.sources.declarative.types import Record + + +@dataclass +class IVRMenusRecordExtractor(RecordExtractor): + def extract_records(self, response: requests.Response) -> List[Record]: + ivrs = response.json().get("ivrs", []) + records = [] + for ivr in ivrs: + for menu in ivr.get("menus", []): + records.append({"ivr_id": ivr["id"], **menu}) + return records + + +@dataclass +class IVRRoutesRecordExtractor(RecordExtractor): + def extract_records(self, response: requests.Response) -> List[Record]: + ivrs = response.json().get("ivrs", []) + records = [] + for ivr in ivrs: + for menu in ivr.get("menus", []): + for route in menu.get("routes", []): + records.append({"ivr_id": ivr["id"], "ivr_menu_id": menu["id"], **route}) + return records + + +@dataclass +class ZendeskTalkAuthenticator(DeclarativeAuthenticator): + config: Mapping[str, Any] + legacy_basic_auth: BasicHttpAuthenticator + basic_auth: BasicHttpAuthenticator + oauth: BearerAuthenticator + + def __new__(cls, legacy_basic_auth, basic_auth, oauth, config, *args, **kwargs): + credentials = config.get("credentials", {}) + if config.get("access_token", {}) and config.get("email", {}): + return legacy_basic_auth + elif credentials["auth_type"] == "api_token": + return basic_auth + elif credentials["auth_type"] == "oauth2.0": + return oauth + else: + raise Exception(f"Missing valid authenticator for auth_type: {credentials['auth_type']}") diff --git a/airbyte-integrations/connectors/source-zendesk-talk/source_zendesk_talk/manifest.yaml b/airbyte-integrations/connectors/source-zendesk-talk/source_zendesk_talk/manifest.yaml new file mode 100644 index 000000000000..ff67384e0a17 --- /dev/null +++ b/airbyte-integrations/connectors/source-zendesk-talk/source_zendesk_talk/manifest.yaml @@ -0,0 +1,1620 @@ +type: DeclarativeSource + +definitions: + authenticator: + class_name: source_zendesk_talk.components.ZendeskTalkAuthenticator + legacy_basic_auth: + type: BasicHttpAuthenticator + password: "{{ config['access_token'] }}" + username: "{{ config['email'] }}/token" + basic_auth: + type: BasicHttpAuthenticator + password: "{{ config['credentials']['api_token'] }}" + username: "{{ config['credentials']['email'] }}/token" + oauth: + type: BearerAuthenticator + api_token: "{{ config['credentials']['access_token'] }}" + non_incremental_paginator: + type: DefaultPaginator + page_token_option: + type: RequestPath + pagination_strategy: + type: CursorPagination + cursor_value: '{{ response.get("next_page", {}) }}' + stop_condition: '{{ not response.get("next_page", {}) }}' + +spec: + type: Spec + documentation_url: https://docs.airbyte.com/integrations/sources/zendesk-talk + connection_specification: + type: object + $schema: http://json-schema.org/draft-07/schema# + title: Source Zendesk Talk Spec + required: + - start_date + - subdomain + properties: + subdomain: + type: string + order: 0 + title: Subdomain + description: This is your Zendesk subdomain that can be found in your account URL. For example, in https://{MY_SUBDOMAIN}.zendesk.com/, where MY_SUBDOMAIN is the value of your subdomain. + credentials: + title: Authentication + type: object + order: 1 + description: "Zendesk service provides two authentication methods. Choose between: `OAuth2.0` or `API token`." + oneOf: + - title: OAuth2.0 + type: object + required: + - access_token + additionalProperties: true + properties: + auth_type: + type: string + const: oauth2.0 + order: 0 + access_token: + type: string + title: Access Token + description: 'The value of the API token generated. See the docs for more information.' + airbyte_secret: true + client_id: + type: string + title: Client ID + description: "Client ID" + airbyte_secret: true + client_secret: + type: string + title: Client Secret + description: "Client Secret" + airbyte_secret: true + - title: API Token + type: object + required: + - email + - api_token + additionalProperties: true + properties: + auth_type: + type: string + const: api_token + email: + title: Email + type: string + description: "The user email for your Zendesk account." + api_token: + title: API Token + type: string + description: 'The value of the API token generated. See the docs for more information.' + airbyte_secret: true + start_date: + type: string + order: 2 + title: Start Date + format: "date-time" + pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$" + description: The date from which you'd like to replicate data for Zendesk Talk API, in the format YYYY-MM-DDT00:00:00Z. All data generated after this date will be replicated. + examples: + - "2020-10-15T00:00:00Z" + advanced_auth: + auth_flow_type: oauth2.0 + predicate_key: + - credentials + - auth_type + predicate_value: oauth2.0 + oauth_config_specification: + complete_oauth_output_specification: + type: object + additionalProperties: false + properties: + access_token: + type: string + path_in_connector_config: + - credentials + - access_token + complete_oauth_server_input_specification: + type: object + additionalProperties: false + properties: + client_id: + type: string + client_secret: + type: string + complete_oauth_server_output_specification: + type: object + additionalProperties: false + properties: + client_id: + type: string + path_in_connector_config: + - credentials + - client_id + client_secret: + type: string + path_in_connector_config: + - credentials + - client_secret + oauth_user_input_from_connector_config_specification: + type: object + additionalProperties: false + properties: + subdomain: + type: string + path_in_connector_config: + - subdomain + +check: + type: CheckStream + stream_names: + - account_overview + +streams: + - name: addresses + type: DeclarativeStream + retriever: + type: SimpleRetriever + paginator: "#/definitions/non_incremental_paginator" + requester: + path: addresses + type: HttpRequester + url_base: "https://{{ config['subdomain'] }}.zendesk.com/api/v2/channels/voice/" + http_method: GET + authenticator: "#/definitions/authenticator" + error_handler: + type: "DefaultErrorHandler" + backoff_strategies: + - type: WaitTimeFromHeader + header: "Retry-After" + request_headers: {} + request_body_json: {} + request_parameters: {} + record_selector: + type: RecordSelector + extractor: + type: DpathExtractor + field_path: + - addresses + - "*" + partition_router: [] + primary_key: + - id + schema_loader: + type: InlineSchemaLoader + schema: + type: object + $schema: http://json-schema.org/schema# + properties: + id: + type: + - "null" + - integer + zip: + type: + - "null" + - string + city: + type: + - "null" + - string + name: + type: + - "null" + - string + street: + type: + - "null" + - string + province: + type: + - "null" + - string + state: + type: + - "null" + - string + country_code: + type: + - "null" + - string + provider_reference: + type: + - "null" + - string + - name: agents_activity + type: DeclarativeStream + retriever: + type: SimpleRetriever + paginator: "#/definitions/non_incremental_paginator" + requester: + path: /stats/agents_activity + type: HttpRequester + url_base: "https://{{ config['subdomain'] }}.zendesk.com/api/v2/channels/voice/" + http_method: GET + authenticator: "#/definitions/authenticator" + error_handler: + type: "DefaultErrorHandler" + backoff_strategies: + - type: WaitTimeFromHeader + header: "Retry-After" + request_headers: {} + request_body_json: {} + request_parameters: {} + record_selector: + type: RecordSelector + extractor: + type: DpathExtractor + field_path: + - agents_activity + - "*" + partition_router: [] + primary_key: agent_id + schema_loader: + type: InlineSchemaLoader + schema: + type: object + $schema: "http://json-schema.org/schema#" + properties: + via: + type: + - "null" + - string + name: + type: + - "null" + - string + agent_id: + type: + - "null" + - integer + call_status: + type: + - "null" + - string + away_time: + type: + - "null" + - integer + avatar_url: + type: + - "null" + - string + agent_state: + type: + - "null" + - string + online_time: + type: + - "null" + - integer + calls_denied: + type: + - "null" + - integer + calls_missed: + type: + - "null" + - integer + available_time: + type: + - "null" + - integer + calls_accepted: + type: + - "null" + - integer + total_hold_time: + type: + - "null" + - integer + total_talk_time: + type: + - "null" + - integer + average_hold_time: + type: + - "null" + - integer + average_talk_time: + type: + - "null" + - integer + calls_put_on_hold: + type: + - "null" + - integer + started_transfers: + type: + - "null" + - integer + accepted_transfers: + type: + - "null" + - integer + total_wrap_up_time: + type: + - "null" + - integer + total_call_duration: + type: + - "null" + - integer + transfers_only_time: + type: + - "null" + - integer + average_wrap_up_time: + type: + - "null" + - integer + started_third_party_conferences: + type: + - "null" + - integer + accepted_third_party_conferences: + type: + - "null" + - integer + forwarding_number: + type: + - "null" + - string + - name: agents_overview + type: DeclarativeStream + retriever: + type: SimpleRetriever + paginator: + type: NoPagination + requester: + path: /stats/agents_overview + type: HttpRequester + url_base: "https://{{ config['subdomain'] }}.zendesk.com/api/v2/channels/voice/" + http_method: GET + authenticator: "#/definitions/authenticator" + error_handler: + type: "DefaultErrorHandler" + backoff_strategies: + - type: WaitTimeFromHeader + header: "Retry-After" + request_headers: {} + request_body_json: {} + request_parameters: {} + record_selector: + type: RecordSelector + extractor: + type: DpathExtractor + field_path: + - agents_overview + partition_router: [] + transformations: + - type: AddFields + fields: + - path: + - "current_timestamp" + # the python implementation didn't normalize to utc, could cause transient problems + value: "{{ now_utc().strftime('%s') }}" + primary_key: [] + schema_loader: + type: InlineSchemaLoader + schema: + type: object + $schema: "http://json-schema.org/schema#" + properties: + total_hold_time: + type: + - "null" + - integer + total_talk_time: + type: + - "null" + - integer + average_away_time: + type: + - "null" + - integer + average_hold_time: + type: + - "null" + - integer + average_talk_time: + type: + - "null" + - integer + total_calls_denied: + type: + - "null" + - integer + total_calls_missed: + type: + - "null" + - integer + total_wrap_up_time: + type: + - "null" + - integer + average_online_time: + type: + - "null" + - integer + average_calls_denied: + type: + - "null" + - integer + average_calls_missed: + type: + - "null" + - integer + average_wrap_up_time: + type: + - "null" + - integer + total_calls_accepted: + type: + - "null" + - integer + average_available_time: + type: + - "null" + - integer + average_calls_accepted: + type: + - "null" + - integer + total_calls_put_on_hold: + type: + - "null" + - integer + total_started_transfers: + type: + - "null" + - integer + total_accepted_transfers: + type: + - "null" + - integer + average_calls_put_on_hold: + type: + - "null" + - integer + average_started_transfers: + type: + - "null" + - integer + average_accepted_transfers: + type: + - "null" + - integer + average_transfers_only_time: + type: + - "null" + - integer + current_timestamp: + type: integer + - name: greeting_categories + type: DeclarativeStream + retriever: + type: SimpleRetriever + paginator: "#/definitions/non_incremental_paginator" + requester: + path: /greeting_categories + type: HttpRequester + url_base: "https://{{ config['subdomain'] }}.zendesk.com/api/v2/channels/voice/" + http_method: GET + authenticator: "#/definitions/authenticator" + error_handler: + type: "DefaultErrorHandler" + backoff_strategies: + - type: WaitTimeFromHeader + header: "Retry-After" + request_headers: {} + request_body_json: {} + request_parameters: {} + record_selector: + type: RecordSelector + extractor: + type: DpathExtractor + field_path: + - greeting_categories + - "*" + partition_router: [] + primary_key: + - id + schema_loader: + type: InlineSchemaLoader + schema: + type: object + $schema: "http://json-schema.org/schema#" + properties: + id: + type: + - "null" + - integer + name: + type: + - "null" + - string + - name: greetings + type: DeclarativeStream + retriever: + type: SimpleRetriever + paginator: "#/definitions/non_incremental_paginator" + requester: + path: /greetings + type: HttpRequester + url_base: "https://{{ config['subdomain'] }}.zendesk.com/api/v2/channels/voice/" + http_method: GET + authenticator: "#/definitions/authenticator" + error_handler: + type: "DefaultErrorHandler" + backoff_strategies: + - type: WaitTimeFromHeader + header: "Retry-After" + request_headers: {} + request_body_json: {} + request_parameters: {} + record_selector: + type: RecordSelector + extractor: + type: DpathExtractor + field_path: + - greetings + - "*" + partition_router: [] + primary_key: [] + schema_loader: + type: InlineSchemaLoader + schema: + type: object + $schema: "http://json-schema.org/schema#" + properties: + id: + type: + - "null" + - string + name: + type: + - "null" + - string + active: + type: + - "null" + - boolean + default: + type: + - "null" + - boolean + ivr_ids: + type: + - "null" + - array + items: + type: + - string + - integer + pending: + type: + - "null" + - boolean + audio_url: + type: + - "null" + - string + audio_name: + type: + - "null" + - string + category_id: + type: + - "null" + - integer + default_lang: + type: + - "null" + - boolean + has_sub_settings: + type: + - "null" + - boolean + phone_number_ids: + type: + - "null" + - array + items: + type: + - integer + - string + upload_id: + type: + - "null" + - integer + - name: phone_numbers + type: DeclarativeStream + retriever: + type: SimpleRetriever + paginator: "#/definitions/non_incremental_paginator" + requester: + path: phone_numbers + type: HttpRequester + url_base: "https://{{ config['subdomain'] }}.zendesk.com/api/v2/channels/voice/" + http_method: GET + authenticator: "#/definitions/authenticator" + error_handler: + type: "DefaultErrorHandler" + backoff_strategies: + - type: WaitTimeFromHeader + header: "Retry-After" + request_headers: {} + request_body_json: {} + request_parameters: {} + record_selector: + type: RecordSelector + extractor: + type: DpathExtractor + field_path: + - phone_numbers + - "*" + partition_router: [] + primary_key: + - id + schema_loader: + type: InlineSchemaLoader + schema: + type: object + $schema: "http://json-schema.org/schema#" + properties: + id: + type: + - "null" + - integer + name: + type: + - "null" + - string + number: + type: + - "null" + - string + external: + type: + - "null" + - boolean + location: + type: + - "null" + - string + priority: + type: + - "null" + - integer + recorded: + type: + - "null" + - boolean + group_ids: + type: + - "null" + - array + line_type: + type: + - "null" + - string + toll_free: + type: + - "null" + - boolean + created_at: + type: + - "null" + - string + sms_enabled: + type: + - "null" + - boolean + capabilities: + type: + - "null" + - object + properties: + mms: + type: + - "null" + - boolean + sms: + type: + - "null" + - boolean + voice: + type: + - "null" + - boolean + emergency_address: + type: + - "null" + - boolean + country_code: + type: + - "null" + - string + greeting_ids: + type: + - "null" + - array + transcription: + type: + - "null" + - boolean + voice_enabled: + type: + - "null" + - boolean + display_number: + type: + - "null" + - string + outbound_enabled: + type: + - "null" + - boolean + default_greeting_ids: + type: + - "null" + - array + items: + type: + - string + categorised_greetings: + type: + - "null" + - object + call_recording_consent: + type: + - "null" + - string + categorised_greetings_with_sub_settings: + type: + - "null" + - object + default_group_id: + type: + - "null" + - integer + failover_number: + type: + - "null" + - string + ivr_id: + type: + - "null" + - integer + nickname: + type: + - "null" + - string + token: + type: + - "null" + - string + schedule_id: + type: + - "null" + - integer + sms_group_id: + type: + - "null" + - integer + - name: call_legs + type: DeclarativeStream + retriever: + type: SimpleRetriever + paginator: + type: DefaultPaginator + page_token_option: + type: RequestPath + pagination_strategy: + type: CursorPagination + cursor_value: '{{ response.get("next_page", {}) }}' + stop_condition: '{{ response.get("count", {}) <= 1 }}' + ignore_stream_slicer_parameters_on_paginated_requests: true + requester: + path: /stats/incremental/legs + type: HttpRequester + url_base: "https://{{ config['subdomain'] }}.zendesk.com/api/v2/channels/voice/" + http_method: GET + authenticator: "#/definitions/authenticator" + error_handler: + type: "DefaultErrorHandler" + backoff_strategies: + - type: WaitTimeFromHeader + header: "Retry-After" + request_headers: {} + request_body_json: {} + request_parameters: {} + record_selector: + type: RecordSelector + extractor: + type: DpathExtractor + field_path: + - legs + - "*" + partition_router: [] + primary_key: + - id + schema_loader: + type: InlineSchemaLoader + schema: + type: object + $schema: "http://json-schema.org/schema#" + properties: + id: + type: + - "null" + - integer + type: + type: + - "null" + - string + call_id: + type: + - "null" + - integer + user_id: + type: + - "null" + - integer + agent_id: + type: + - "null" + - integer + duration: + type: + - "null" + - integer + hold_time: + type: + - "null" + - integer + talk_time: + type: + - "null" + - integer + created_at: + type: + - "null" + - string + updated_at: + type: + - "null" + - string + format: "date-time" + call_charge: + type: + - "null" + - string + wrap_up_time: + type: + - "null" + - integer + available_via: + type: + - "null" + - string + minutes_billed: + type: + - "null" + - integer + quality_issues: + type: + - "null" + - array + completion_status: + type: + - "null" + - string + conference_from: + type: + - "null" + - integer + conference_time: + type: + - "null" + - integer + conference_to: + type: + - "null" + - integer + consultation_from: + type: + - "null" + - integer + consultation_time: + type: + - "null" + - integer + consultation_to: + type: + - "null" + - integer + forwarded_to: + type: + - "null" + - string + transferred_from: + type: + - "null" + - integer + transferred_to: + type: + - "null" + - integer + incremental_sync: + type: DatetimeBasedCursor + cursor_field: updated_at + start_datetime: + type: MinMaxDatetime + datetime: "{{ config['start_date'] }}" + datetime_format: "%Y-%m-%dT%H:%M:%SZ" + datetime_format: "%s" + start_time_option: + type: RequestOption + field_name: start_time + inject_into: request_parameter + cursor_datetime_formats: + - "%Y-%m-%dT%H:%M:%SZ" + - name: calls + type: DeclarativeStream + retriever: + type: SimpleRetriever + paginator: + type: DefaultPaginator + page_token_option: + type: RequestPath + pagination_strategy: + type: CursorPagination + cursor_value: '{{ response.get("next_page", {}) }}' + stop_condition: '{{ response.get("count", {}) <= 1 }}' + ignore_stream_slicer_parameters_on_paginated_requests: true + requester: + path: /stats/incremental/calls + type: HttpRequester + url_base: "https://{{ config['subdomain'] }}.zendesk.com/api/v2/channels/voice/" + http_method: GET + authenticator: "#/definitions/authenticator" + error_handler: + type: "DefaultErrorHandler" + backoff_strategies: + - type: WaitTimeFromHeader + header: "Retry-After" + request_headers: {} + request_body_json: {} + request_parameters: {} + record_selector: + type: RecordSelector + extractor: + type: DpathExtractor + field_path: + - calls + - "*" + partition_router: [] + primary_key: + - id + schema_loader: + type: InlineSchemaLoader + schema: + type: object + $schema: "http://json-schema.org/schema#" + properties: + id: + type: + - integer + - "null" + callback: + type: + - boolean + - "null" + duration: + type: + - integer + - "null" + direction: + type: + - string + - "null" + hold_time: + type: + - integer + - "null" + talk_time: + type: + - integer + - "null" + voicemail: + type: + - boolean + - "null" + wait_time: + type: + - integer + - "null" + created_at: + type: + - string + - "null" + overflowed: + type: + - boolean + - "null" + updated_at: + type: + - string + - "null" + format: "date-time" + call_charge: + type: + - string + - "null" + phone_number: + type: + - string + - "null" + wrap_up_time: + type: + - integer + - "null" + default_group: + type: + - boolean + - "null" + minutes_billed: + type: + - integer + - "null" + quality_issues: + type: + - array + - "null" + recording_time: + type: + - integer + - "null" + phone_number_id: + type: + - integer + - "null" + completion_status: + type: + - string + - "null" + consultation_time: + type: + - integer + - "null" + not_recording_time: + type: + - integer + - "null" + call_recording_consent: + type: + - string + - "null" + outside_business_hours: + type: + - boolean + - "null" + exceeded_queue_wait_time: + type: + - boolean + - "null" + customer_requested_voicemail: + type: + - boolean + - "null" + recording_control_interactions: + type: + - integer + - "null" + agent_id: + type: + - integer + - "null" + call_group_id: + type: + - integer + - "null" + call_recording_consent_action: + type: + - string + - "null" + call_recording_consent_keypress: + type: + - string + - "null" + callback_source: + type: + - string + - "null" + exceeded_queue_time: + type: + - boolean + - "null" + ivr_action: + type: + - string + - "null" + ivr_destination_group_name: + type: + - string + - "null" + ivr_hops: + type: + - integer + - "null" + ivr_routed_to: + type: + - string + - "null" + ivr_time_spent: + type: + - integer + - "null" + overflowed_to: + type: + - string + - "null" + ticket_id: + type: + - integer + - "null" + time_to_answer: + type: + - integer + - "null" + incremental_sync: + type: DatetimeBasedCursor + cursor_field: updated_at + start_datetime: + type: MinMaxDatetime + datetime: "{{ config['start_date'] }}" + datetime_format: "%Y-%m-%dT%H:%M:%SZ" + datetime_format: "%s" + start_time_option: + type: RequestOption + field_name: start_time + inject_into: request_parameter + cursor_datetime_formats: + - "%Y-%m-%dT%H:%M:%SZ" + - name: current_queue_activity + type: DeclarativeStream + retriever: + type: SimpleRetriever + paginator: + type: NoPagination + requester: + path: /stats/current_queue_activity + type: HttpRequester + url_base: "https://{{ config['subdomain'] }}.zendesk.com/api/v2/channels/voice/" + http_method: GET + authenticator: "#/definitions/authenticator" + error_handler: + type: "DefaultErrorHandler" + backoff_strategies: + - type: WaitTimeFromHeader + header: "Retry-After" + request_headers: {} + request_body_json: {} + request_parameters: {} + record_selector: + type: RecordSelector + extractor: + type: DpathExtractor + field_path: + - current_queue_activity + partition_router: [] + transformations: + - type: AddFields + fields: + - path: + - "current_timestamp" + # the python implementation didn't normalize to utc, could cause transient problems + value: "{{ now_utc().strftime('%s') }}" + primary_key: [] + schema_loader: + type: InlineSchemaLoader + schema: + type: object + $schema: "http://json-schema.org/schema#" + properties: + agents_online: + type: + - integer + - "null" + calls_waiting: + type: + - integer + - "null" + average_wait_time: + type: + - integer + - "null" + callbacks_waiting: + type: + - integer + - "null" + longest_wait_time: + type: + - integer + - "null" + embeddable_callbacks_waiting: + type: + - integer + - "null" + current_timestamp: + type: integer + - name: account_overview + type: DeclarativeStream + retriever: + type: SimpleRetriever + paginator: + type: NoPagination + requester: + path: /stats/account_overview + type: HttpRequester + url_base: "https://{{ config['subdomain'] }}.zendesk.com/api/v2/channels/voice/" + http_method: GET + authenticator: "#/definitions/authenticator" + error_handler: + type: "DefaultErrorHandler" + backoff_strategies: + - type: WaitTimeFromHeader + header: "Retry-After" + request_headers: {} + request_body_json: {} + request_parameters: {} + record_selector: + type: RecordSelector + extractor: + type: DpathExtractor + field_path: + - account_overview + partition_router: [] + transformations: + - type: AddFields + fields: + - path: + - "current_timestamp" + # the python implementation didn't normalize to utc, could cause transient problems + value: "{{ now_utc().strftime('%s') }}" + primary_key: [] + schema_loader: + type: InlineSchemaLoader + schema: + type: object + $schema: "http://json-schema.org/schema#" + properties: + current_timestamp: + type: integer + total_calls: + type: + - integer + - "null" + total_hold_time: + type: + - integer + - "null" + total_voicemails: + type: + - integer + - "null" + average_hold_time: + type: + - integer + - "null" + max_calls_waiting: + type: + - integer + - "null" + total_wrap_up_time: + type: + - integer + - "null" + max_queue_wait_time: + type: + - integer + - "null" + total_call_duration: + type: + - integer + - "null" + total_inbound_calls: + type: + - integer + - "null" + average_wrap_up_time: + type: + - integer + - "null" + total_callback_calls: + type: + - integer + - "null" + total_outbound_calls: + type: + - integer + - "null" + average_call_duration: + type: + - integer + - "null" + average_time_to_answer: + type: + - integer + - "null" + average_queue_wait_time: + type: + - integer + - "null" + total_textback_requests: + type: + - integer + - "null" + average_callback_wait_time: + type: + - integer + - "null" + total_calls_abandoned_in_queue: + type: + - integer + - "null" + total_embeddable_callback_calls: + type: + - integer + - "null" + total_calls_outside_business_hours: + type: + - integer + - "null" + total_calls_with_requested_voicemail: + type: + - integer + - "null" + total_calls_with_exceeded_queue_wait_time: + type: + - integer + - "null" + - name: ivrs + type: DeclarativeStream + retriever: + type: SimpleRetriever + paginator: "#/definitions/non_incremental_paginator" + requester: + path: /ivr + type: HttpRequester + url_base: "https://{{ config['subdomain'] }}.zendesk.com/api/v2/channels/voice/" + http_method: GET + authenticator: "#/definitions/authenticator" + error_handler: + type: "DefaultErrorHandler" + backoff_strategies: + - type: WaitTimeFromHeader + header: "Retry-After" + request_headers: {} + request_body_json: {} + request_parameters: {} + record_selector: + type: RecordSelector + extractor: + type: DpathExtractor + field_path: + - ivrs + partition_router: [] + primary_key: + - id + schema_loader: + type: InlineSchemaLoader + schema: + type: object + $schema: "http://json-schema.org/schema#" + properties: + id: + type: + - integer + - "null" + name: + type: + - string + - "null" + menus: + type: + - array + - "null" + items: + type: object + properties: + id: + type: + - integer + - "null" + name: + type: + - string + - "null" + greeting_id: + type: + - integer + - "null" + routes: + type: + - array + - "null" + items: + type: object + properties: + id: + type: + - integer + - "null" + action: + type: + - string + - "null" + greeting: + type: + - string + - "null" + options: + type: + - object + - "null" + keypress: + type: + - string + - "null" + option_text: + type: + - string + - "null" + overflow_options: + type: + - array + - "null" + default: + type: + - boolean + - "null" + phone_number_ids: + type: + - array + - "null" + items: + type: + - string + - integer + phone_number_names: + type: + - array + - "null" + items: + type: + - string + - integer + - name: ivr_menus + type: DeclarativeStream + retriever: + type: SimpleRetriever + paginator: "#/definitions/non_incremental_paginator" + requester: + path: /ivr + type: HttpRequester + url_base: "https://{{ config['subdomain'] }}.zendesk.com/api/v2/channels/voice/" + http_method: GET + authenticator: "#/definitions/authenticator" + error_handler: + type: "DefaultErrorHandler" + backoff_strategies: + - type: WaitTimeFromHeader + header: "Retry-After" + request_headers: {} + request_body_json: {} + request_parameters: {} + record_selector: + type: RecordSelector + extractor: + type: CustomRecordExtractor + class_name: source_zendesk_talk.components.IVRMenusRecordExtractor + partition_router: [] + primary_key: [] + schema_loader: + type: InlineSchemaLoader + schema: + type: object + $schema: "http://json-schema.org/schema#" + properties: + id: + type: + - integer + - "null" + name: + type: + - string + - "null" + ivr_id: + type: + - integer + - "null" + greeting_id: + type: + - "null" + - integer + default: + type: + - boolean + - "null" + - name: ivr_routes + type: DeclarativeStream + retriever: + type: SimpleRetriever + paginator: "#/definitions/non_incremental_paginator" + requester: + path: /ivr + type: HttpRequester + url_base: "https://{{ config['subdomain'] }}.zendesk.com/api/v2/channels/voice/" + http_method: GET + authenticator: "#/definitions/authenticator" + error_handler: + type: "DefaultErrorHandler" + backoff_strategies: + - type: WaitTimeFromHeader + header: "Retry-After" + request_headers: {} + request_body_json: {} + request_parameters: {} + record_selector: + type: RecordSelector + extractor: + type: CustomRecordExtractor + class_name: source_zendesk_talk.components.IVRRoutesRecordExtractor + partition_router: [] + primary_key: [] + schema_loader: + type: InlineSchemaLoader + schema: + type: object + $schema: "http://json-schema.org/schema#" + properties: + action: + type: + - string + - "null" + greeting: + type: + - string + - "null" + id: + type: + - integer + - "null" + ivr_id: + type: + - integer + - "null" + ivr_menu_id: + type: + - integer + - "null" + keypress: + type: + - string + - "null" + option_text: + type: + - string + - "null" + options: + type: + - object + - "null" + overflow_options: + type: + - array + - "null" +version: 0.65.0 diff --git a/airbyte-integrations/connectors/source-zendesk-talk/source_zendesk_talk/source.py b/airbyte-integrations/connectors/source-zendesk-talk/source_zendesk_talk/source.py index 47ab8d0a9761..87aa38b3c918 100644 --- a/airbyte-integrations/connectors/source-zendesk-talk/source_zendesk_talk/source.py +++ b/airbyte-integrations/connectors/source-zendesk-talk/source_zendesk_talk/source.py @@ -2,76 +2,9 @@ # Copyright (c) 2023 Airbyte, Inc., all rights reserved. # -from typing import Any, List, Mapping, Tuple +from airbyte_cdk.sources.declarative.yaml_declarative_source import YamlDeclarativeSource -import pendulum -import requests -from airbyte_cdk import AirbyteLogger -from airbyte_cdk.models import SyncMode -from airbyte_cdk.sources import AbstractSource -from airbyte_cdk.sources.streams import Stream -from airbyte_cdk.sources.streams.http.requests_native_auth import TokenAuthenticator -from requests.auth import HTTPBasicAuth -from source_zendesk_talk.streams import ( - AccountOverview, - Addresses, - AgentsActivity, - AgentsOverview, - CallLegs, - Calls, - CurrentQueueActivity, - GreetingCategories, - Greetings, - IVRMenus, - IVRRoutes, - IVRs, - PhoneNumbers, -) - -class SourceZendeskTalk(AbstractSource): - @classmethod - def get_authenticator(cls, config: Mapping[str, Any]) -> requests.auth.AuthBase: - # old authentication flow support - if "access_token" in config and "email" in config: - return HTTPBasicAuth(username=f'{config["email"]}/token', password=config["access_token"]) - # new authentication flow - auth = config["credentials"] - if auth: - if auth["auth_type"] == "oauth2.0": - return TokenAuthenticator(token=auth["access_token"]) - elif auth["auth_type"] == "api_token": - return HTTPBasicAuth(username=f'{auth["email"]}/token', password=auth["api_token"]) - else: - raise Exception(f"Not implemented authorization method: {auth['auth_type']}") - - def check_connection(self, logger: AirbyteLogger, config: Mapping[str, Any]) -> Tuple[bool, Any]: - authenticator = self.get_authenticator(config) - stream = AccountOverview(authenticator=authenticator, subdomain=config["subdomain"]) - - account_info = next(iter(stream.read_records(sync_mode=SyncMode.full_refresh)), None) - if not account_info: - raise RuntimeError("Unable to read account information, please check the permissions of your token") - - return True, None - - def streams(self, config: Mapping[str, Any]) -> List[Stream]: - authenticator = self.get_authenticator(config) - common_kwargs = {"authenticator": authenticator, "subdomain": config["subdomain"]} - incremental_kwargs = {**common_kwargs, **{"start_date": pendulum.parse(config["start_date"])}} - - return [ - AccountOverview(**common_kwargs), - Addresses(**common_kwargs), - AgentsActivity(**common_kwargs), - AgentsOverview(**common_kwargs), - Calls(**incremental_kwargs), - CallLegs(**incremental_kwargs), - CurrentQueueActivity(**common_kwargs), - Greetings(**common_kwargs), - GreetingCategories(**common_kwargs), - IVRMenus(**common_kwargs), - IVRRoutes(**common_kwargs), - IVRs(**common_kwargs), - PhoneNumbers(**common_kwargs), - ] +class SourceZendeskTalk(YamlDeclarativeSource): + def __init__(self): + super().__init__(**{"path_to_yaml": "manifest.yaml"}) diff --git a/airbyte-integrations/connectors/source-zendesk-talk/source_zendesk_talk/spec.json b/airbyte-integrations/connectors/source-zendesk-talk/source_zendesk_talk/spec.json deleted file mode 100644 index 8268b8449a59..000000000000 --- a/airbyte-integrations/connectors/source-zendesk-talk/source_zendesk_talk/spec.json +++ /dev/null @@ -1,141 +0,0 @@ -{ - "documentationUrl": "https://docs.airbyte.com/integrations/sources/zendesk-talk", - "connectionSpecification": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Source Zendesk Talk Spec", - "type": "object", - "required": ["start_date", "subdomain"], - "properties": { - "subdomain": { - "type": "string", - "title": "Subdomain", - "order": 0, - "description": "This is your Zendesk subdomain that can be found in your account URL. For example, in https://{MY_SUBDOMAIN}.zendesk.com/, where MY_SUBDOMAIN is the value of your subdomain." - }, - "credentials": { - "title": "Authentication", - "type": "object", - "order": 1, - "description": "Zendesk service provides two authentication methods. Choose between: `OAuth2.0` or `API token`.", - "oneOf": [ - { - "title": "OAuth2.0", - "type": "object", - "required": ["access_token"], - "additionalProperties": true, - "properties": { - "auth_type": { - "type": "string", - "const": "oauth2.0", - "order": 0 - }, - "access_token": { - "type": "string", - "title": "Access Token", - "description": "The value of the API token generated. See the docs for more information.", - "airbyte_secret": true - }, - "client_id": { - "type": "string", - "title": "Client ID", - "description": "Client ID", - "airbyte_secret": true - }, - "client_secret": { - "type": "string", - "title": "Client Secret", - "description": "Client Secret", - "airbyte_secret": true - } - } - }, - { - "title": "API Token", - "type": "object", - "required": ["email", "api_token"], - "additionalProperties": true, - "properties": { - "auth_type": { - "type": "string", - "const": "api_token" - }, - "email": { - "title": "Email", - "type": "string", - "description": "The user email for your Zendesk account." - }, - "api_token": { - "title": "API Token", - "type": "string", - "description": "The value of the API token generated. See the docs for more information.", - "airbyte_secret": true - } - } - } - ] - }, - "start_date": { - "type": "string", - "title": "Start Date", - "order": 2, - "description": "The date from which you'd like to replicate data for Zendesk Talk API, in the format YYYY-MM-DDT00:00:00Z. All data generated after this date will be replicated.", - "examples": ["2020-10-15T00:00:00Z"], - "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$", - "format": "date-time" - } - } - }, - "advanced_auth": { - "auth_flow_type": "oauth2.0", - "predicate_key": ["credentials", "auth_type"], - "predicate_value": "oauth2.0", - "oauth_config_specification": { - "complete_oauth_output_specification": { - "type": "object", - "additionalProperties": false, - "properties": { - "access_token": { - "type": "string", - "path_in_connector_config": ["credentials", "access_token"] - } - } - }, - "complete_oauth_server_input_specification": { - "type": "object", - "additionalProperties": false, - "properties": { - "client_id": { - "type": "string" - }, - "client_secret": { - "type": "string" - } - } - }, - "complete_oauth_server_output_specification": { - "type": "object", - "additionalProperties": false, - "properties": { - "client_id": { - "type": "string", - "path_in_connector_config": ["credentials", "client_id"] - }, - "client_secret": { - "type": "string", - "path_in_connector_config": ["credentials", "client_secret"] - } - } - }, - "oauth_user_input_from_connector_config_specification": { - "type": "object", - "additionalProperties": false, - "properties": { - "subdomain": { - "type": "string", - "path_in_connector_config": ["subdomain"] - } - } - } - } - } -} diff --git a/airbyte-integrations/connectors/source-zendesk-talk/source_zendesk_talk/streams.py b/airbyte-integrations/connectors/source-zendesk-talk/source_zendesk_talk/streams.py deleted file mode 100644 index f017988cd461..000000000000 --- a/airbyte-integrations/connectors/source-zendesk-talk/source_zendesk_talk/streams.py +++ /dev/null @@ -1,317 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - - -from abc import ABC, abstractmethod -from datetime import datetime -from typing import Any, Iterable, Mapping, MutableMapping, Optional -from urllib.parse import parse_qs, urlparse - -import pendulum as pendulum -import requests -from airbyte_cdk.sources.streams.availability_strategy import AvailabilityStrategy -from airbyte_cdk.sources.streams.http import HttpStream - - -class ZendeskTalkStream(HttpStream, ABC): - """Base class for streams""" - - primary_key = "id" - - def __init__(self, subdomain: str, **kwargs): - """Constructor, accepts subdomain to calculate correct url""" - super().__init__(**kwargs) - self._subdomain = subdomain - - @property - @abstractmethod - def data_field(self) -> str: - """Specifies root object name in a stream response""" - - @property - def url_base(self) -> str: - """API base url based on configured subdomain""" - return f"https://{self._subdomain}.zendesk.com/api/v2/channels/voice/" - - @property - def availability_strategy(self) -> Optional["AvailabilityStrategy"]: - return None - - def backoff_time(self, response: requests.Response) -> Optional[float]: - """ - Override this method to dynamically determine backoff time e.g: by reading the X-Retry-After header. - - This method is called only if should_backoff() returns True for the input request. - - :return how long to backoff in seconds. The return value may be a floating point number for subsecond precision. Returning None defers backoff - to the default backoff behavior (e.g using an exponential algorithm). - """ - delay_time = response.headers.get("Retry-After") - if delay_time: - return int(delay_time) - return None - - def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]: - """ - This method should return a Mapping (e.g: dict) containing whatever information required to make paginated requests. This dict is passed - to most other methods in this class to help you form headers, request bodies, query params, etc.. - - :param response: the most recent response from the API - :return If there is another page in the result, a mapping (e.g: dict) containing information needed to query the next page in the response. - If there are no more pages in the result, return None. - """ - response_json = response.json() - next_page_url = response_json.get("next_page") - if next_page_url: - next_url = urlparse(next_page_url) - next_params = parse_qs(next_url.query) - return next_params - - return None - - def request_params( - self, - stream_state: Mapping[str, Any], - stream_slice: Optional[Mapping[str, Any]] = None, - next_page_token: Optional[Mapping[str, Any]] = None, - ) -> MutableMapping[str, Any]: - """Usually contains common params e.g. pagination size etc.""" - return dict(next_page_token or {}) - - def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]: - """Simply parse json and iterates over root object""" - response_json = response.json() - if self.data_field: - response_json = response_json[self.data_field] - - if not isinstance(response_json, list): - response_json = [response_json] - - yield from response_json - - -class ZendeskTalkIncrementalStream(ZendeskTalkStream, ABC): - """Stream that supports state and incremental read, for now only incremental export endpoints use this class. - Docs: https://developer.zendesk.com/api-reference/ticketing/ticket-management/incremental_exports - """ - - # required to support old format as well (only read, but save as new) - legacy_cursor_field = "timestamp" - cursor_field = "updated_at" - filter_param = "start_time" - - def __init__(self, start_date: datetime, **kwargs): - super().__init__(**kwargs) - self._start_date = pendulum.instance(start_date) - - def get_updated_state(self, current_stream_state: MutableMapping[str, Any], latest_record: Mapping[str, Any]) -> Mapping[str, Any]: - """ - Override to determine the latest state after reading the latest record. This typically compared the cursor_field from the latest record and - the current state and picks the 'most' recent cursor. This is how a stream's state is determined. Required for incremental. - """ - latest_state = current_stream_state.get(self.cursor_field, current_stream_state.get(self.legacy_cursor_field)) - new_cursor_value = max(latest_record[self.cursor_field], latest_state or latest_record[self.cursor_field]) - return {self.cursor_field: new_cursor_value} - - def request_params(self, stream_state=None, **kwargs): - """Add incremental parameters""" - params = super().request_params(stream_state=stream_state, **kwargs) - - if self.filter_param not in params: - # use cursor as filter value only if it is not already a parameter (i.e. we are in the middle of the pagination) - stream_state = stream_state or {} - state_str = stream_state.get(self.cursor_field, stream_state.get(self.legacy_cursor_field)) - state = pendulum.parse(state_str) if state_str else self._start_date - params[self.filter_param] = max(state, self._start_date).int_timestamp - - return params - - def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]: - """ - This method should return a Mapping (e.g: dict) containing whatever information required to make paginated requests. This dict is passed - to most other methods in this class to help you form headers, request bodies, query params, etc.. - - :param response: the most recent response from the API - :return If there is another page in the result, a mapping (e.g: dict) containing information needed to query the next page in the response. - If there are no more pages in the result, return None. - """ - next_params = super().next_page_token(response) - if not next_params: - return None - - current_url = urlparse(response.request.url) - current_params = parse_qs(current_url.query) - - # check if cursor value was changed - if current_params[self.filter_param] != next_params[self.filter_param]: - return next_params - - return None - - -class ZendeskTalkSingleRecordStream(ZendeskTalkStream, ABC): - primary_key = "current_timestamp" - - def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]: - for record in super().parse_response(response, **kwargs): - record["current_timestamp"] = pendulum.now().int_timestamp - yield record - - -class PhoneNumbers(ZendeskTalkStream): - """Phone Numbers - Docs: https://developer.zendesk.com/api-reference/voice/talk-api/phone_numbers/#list-phone-numbers - """ - - data_field = "phone_numbers" - - def path(self, **kwargs) -> str: - return "phone_numbers" - - -class Addresses(ZendeskTalkStream): - """Addresses - Docs: https://developer.zendesk.com/api-reference/voice/talk-api/addresses/#list-addresses - """ - - data_field = "addresses" - - def path(self, **kwargs) -> str: - return "addresses" - - -class GreetingCategories(ZendeskTalkStream): - """Greeting Categories - Docs: https://developer.zendesk.com/rest_api/docs/voice-api/greetings#list-greeting-categories - """ - - data_field = "greeting_categories" - - def path(self, **kwargs) -> str: - return "greeting_categories" - - -class Greetings(ZendeskTalkStream): - """Greetings - Docs: https://developer.zendesk.com/rest_api/docs/voice-api/greetings#list-greetings - """ - - data_field = "greetings" - - def path(self, **kwargs) -> str: - return "greetings" - - -class IVRs(ZendeskTalkStream): - """IVRs - Docs: https://developer.zendesk.com/rest_api/docs/voice-api/ivrs#list-ivrs - """ - - name = "ivrs" - data_field = "ivrs" - use_cache = True - cache_filename = "ivrs.yml" - - def path(self, **kwargs) -> str: - return "ivr.json" - - -class IVRMenus(IVRs): - """IVR Menus - Docs: https://developer.zendesk.com/rest_api/docs/voice-api/ivrs#list-ivrs - """ - - name = "ivr_menus" - - def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]: - """Simply parse json and iterates over root object""" - ivrs = super().parse_response(response=response, **kwargs) - for ivr in ivrs: - for menu in ivr["menus"]: - yield {"ivr_id": ivr["id"], **menu} - - -class IVRRoutes(IVRs): - """IVR Routes - Docs: https://developer.zendesk.com/rest_api/docs/voice-api/ivr_routes#list-ivr-routes - """ - - name = "ivr_routes" - - def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]: - """Simply parse json and iterates over root object""" - ivrs = super().parse_response(response=response, **kwargs) - for ivr in ivrs: - for menu in ivr["menus"]: - for route in ivr["menus"]: - yield {"ivr_id": ivr["id"], "ivr_menu_id": menu["id"], **route} - - -class AccountOverview(ZendeskTalkSingleRecordStream): - """Account Overview - Docs: https://developer.zendesk.com/rest_api/docs/voice-api/stats#show-account-overview - """ - - data_field = "account_overview" - - def path(self, **kwargs) -> str: - return "stats/account_overview" - - -class AgentsActivity(ZendeskTalkStream): - """Agents Activity - Docs: https://developer.zendesk.com/rest_api/docs/voice-api/stats#list-agents-activity - """ - - data_field = "agents_activity" - primary_key = "agent_id" - - def path(self, **kwargs) -> str: - return "stats/agents_activity" - - -class AgentsOverview(ZendeskTalkSingleRecordStream): - """Agents Overview - Docs: https://developer.zendesk.com/rest_api/docs/voice-api/stats#show-agents-overview - """ - - data_field = "agents_overview" - - def path(self, **kwargs) -> str: - return "stats/agents_overview" - - -class CurrentQueueActivity(ZendeskTalkSingleRecordStream): - """Current Queue Activity - Docs: https://developer.zendesk.com/rest_api/docs/voice-api/stats#show-current-queue-activity - """ - - data_field = "current_queue_activity" - - def path(self, **kwargs) -> str: - return "stats/current_queue_activity" - - -class Calls(ZendeskTalkIncrementalStream): - """Calls - Docs: https://developer.zendesk.com/rest_api/docs/voice-api/incremental_exports#incremental-calls-export - """ - - data_field = "calls" - cursor_field = "updated_at" - - def path(self, **kwargs) -> str: - return "stats/incremental/calls" - - -class CallLegs(ZendeskTalkIncrementalStream): - """Call Legs - Docs: https://developer.zendesk.com/rest_api/docs/voice-api/incremental_exports#incremental-call-legs-export - """ - - data_field = "legs" - cursor_field = "updated_at" - - def path(self, **kwargs) -> str: - return "stats/incremental/legs" diff --git a/airbyte-integrations/connectors/source-zendesk-talk/unit_tests/test_components.py b/airbyte-integrations/connectors/source-zendesk-talk/unit_tests/test_components.py new file mode 100644 index 000000000000..6dc83fe5a28f --- /dev/null +++ b/airbyte-integrations/connectors/source-zendesk-talk/unit_tests/test_components.py @@ -0,0 +1,75 @@ +# Copyright (c) 2023 Airbyte, Inc., all rights reserved. + +from unittest.mock import MagicMock + +import pytest +import requests +import requests_mock +from airbyte_cdk.sources.declarative.auth.token import BasicHttpAuthenticator, BearerAuthenticator +from source_zendesk_talk.components import IVRMenusRecordExtractor, IVRRoutesRecordExtractor, ZendeskTalkAuthenticator + + +@pytest.mark.parametrize( + "response_data, expected_records", + [ + # Test cases for IVRMenusRecordExtractor + ( + {"ivrs": [{"id": "ivr_1", "menus": [{"id": "menu_1a", "name": "Menu 1A"}, {"id": "menu_1b", "name": "Menu 1B"}]}, {"id": "ivr_2", "menus": [{"id": "menu_2a", "name": "Menu 2A"}]}]}, + [{"ivr_id": "ivr_1", "id": "menu_1a", "name": "Menu 1A"}, {"ivr_id": "ivr_1", "id": "menu_1b", "name": "Menu 1B"}, {"ivr_id": "ivr_2", "id": "menu_2a", "name": "Menu 2A"}] + ), + ({"ivrs": []}, []), + ({"ivrs": [{"id": "ivr_1", "menus": []}]}, []), + ] +) +def test_ivr_menus_record_extractor(response_data, expected_records): + with requests_mock.Mocker() as m: + m.get('https://not-the-real.api/ivrs', json=response_data) + response = requests.get('https://not-the-real.api/ivrs') + + extractor = IVRMenusRecordExtractor() + records = extractor.extract_records(response) + + assert records == expected_records + +@pytest.mark.parametrize( + "response_data, expected_records", + [ + # Test cases for IVRRoutesRecordExtractor + ( + {"ivrs": [{"id": "ivr_1", "menus": [{"id": "menu_1a", "routes": [{"id": "route_1a1", "name": "Route 1A1"}, {"id": "route_1a2", "name": "Route 1A2"}]}]}]}, + [{"ivr_id": "ivr_1", "ivr_menu_id": "menu_1a", "id": "route_1a1", "name": "Route 1A1"}, {"ivr_id": "ivr_1", "ivr_menu_id": "menu_1a", "id": "route_1a2", "name": "Route 1A2"}] + ), + ({"ivrs": [{"id": "ivr_1", "menus": [{"id": "menu_1a", "routes": []}]}]}, []), + ] +) +def test_ivr_routes_record_extractor(response_data, expected_records): + with requests_mock.Mocker() as m: + m.get('https://not-the-real.api/ivrs', json=response_data) + response = requests.get('https://not-the-real.api/ivrs') + + extractor = IVRRoutesRecordExtractor() + records = extractor.extract_records(response) + + assert records == expected_records + +@pytest.mark.parametrize( + "config, authenticator_type", + [ + ({"access_token": "dummy_token", "email": "dummy@example.com"}, BasicHttpAuthenticator), + ({"credentials": {"auth_type": "api_token"}}, BasicHttpAuthenticator), + ({"credentials": {"auth_type": "oauth2.0"}}, BearerAuthenticator), + ] +) +def test_zendesk_talk_authenticator(config, authenticator_type): + legacy_basic_auth = MagicMock(spec=BasicHttpAuthenticator) + basic_auth = MagicMock(spec=BasicHttpAuthenticator) + oauth = MagicMock(spec=BearerAuthenticator) + + authenticator = ZendeskTalkAuthenticator(legacy_basic_auth, basic_auth, oauth, config) + assert isinstance(authenticator, authenticator_type) + +def test_zendesk_talk_authenticator_invalid(): + with pytest.raises(Exception) as excinfo: + config = {"credentials": {"auth_type": "invalid"}} + ZendeskTalkAuthenticator(None, None, None, config) + assert "Missing valid authenticator" in str(excinfo.value) diff --git a/airbyte-integrations/connectors/source-zendesk-talk/unit_tests/test_source.py b/airbyte-integrations/connectors/source-zendesk-talk/unit_tests/test_source.py deleted file mode 100644 index 81ac87db3ac5..000000000000 --- a/airbyte-integrations/connectors/source-zendesk-talk/unit_tests/test_source.py +++ /dev/null @@ -1,97 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import pendulum -import pytest -from airbyte_cdk.models import AirbyteConnectionStatus, Status -from airbyte_cdk.sources.streams.http import HttpStream -from source_zendesk_talk import SourceZendeskTalk - - -@pytest.fixture -def patch_base_class_oauth20(mocker): - return { - "config": { - "credentials": {"auth_type": "oauth2.0", "access_token": "accesstoken"}, - "subdomain": "airbyte-subdomain", - "start_date": "2021-04-01T00:00:00Z", - } - } - - -@pytest.fixture -def patch_base_class_api_token(mocker): - return { - "config": { - "credentials": {"auth_type": "api_token", "api_token": "accesstoken", "email": "email@example.com"}, - "subdomain": "airbyte-subdomain", - "start_date": "2021-04-01T00:00:00Z", - } - } - - -def test_check_connection_oauth20(mocker, patch_base_class_oauth20): - source = SourceZendeskTalk() - - logger_mock, config_mock = mocker.MagicMock(), mocker.MagicMock() - config_mock.__getitem__.side_effect = patch_base_class_oauth20["config"].__getitem__ - - mocker.patch.object(HttpStream, "read_records", return_value=[mocker.MagicMock()]) - assert source.check(logger_mock, config_mock) == AirbyteConnectionStatus(status=Status.SUCCEEDED) - - -def test_check_connection_api_token(mocker, patch_base_class_api_token): - source = SourceZendeskTalk() - - logger_mock, config_mock = mocker.MagicMock(), mocker.MagicMock() - config_mock.__getitem__.side_effect = patch_base_class_api_token["config"].__getitem__ - - mocker.patch.object(HttpStream, "read_records", return_value=[mocker.MagicMock()]) - assert source.check(logger_mock, config_mock) == AirbyteConnectionStatus(status=Status.SUCCEEDED) - - -def test_streams(mocker, patch_base_class_oauth20): - source = SourceZendeskTalk() - - config_mock = mocker.MagicMock() - config_mock.__getitem__.side_effect = patch_base_class_oauth20["config"].__getitem__ - - all_streams = source.streams(config_mock) - - expected_streams_number = 13 - streams = filter( - lambda s: s.__class__.__name__ - in [ - "AccountOverview", - "Addresses", - "AgentsActivity", - "AgentsOverview", - "CurrentQueueActivity", - "Greetings", - "GreetingCategories", - "IVRMenus", - "IVRRoutes", - "IVRs", - "PhoneNumbers", - ], - all_streams, - ) - - incremental_streams = filter( - lambda s: s.__class__.__name__ - in [ - "Calls", - "CallLegs", - ], - all_streams, - ) - - assert len(all_streams) == expected_streams_number - - for s in incremental_streams: - assert s._start_date == pendulum.parse(patch_base_class_oauth20["config"]["start_date"]) - assert s._subdomain == "airbyte-subdomain" - - for s in streams: - assert s._subdomain == "airbyte-subdomain" diff --git a/airbyte-integrations/connectors/source-zendesk-talk/unit_tests/test_streams.py b/airbyte-integrations/connectors/source-zendesk-talk/unit_tests/test_streams.py deleted file mode 100644 index 443479a3ec95..000000000000 --- a/airbyte-integrations/connectors/source-zendesk-talk/unit_tests/test_streams.py +++ /dev/null @@ -1,276 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import random -from urllib.parse import urlparse - -import pendulum -import pytest -import requests -from source_zendesk_talk.streams import IVRMenus, IVRRoutes, ZendeskTalkIncrementalStream, ZendeskTalkSingleRecordStream, ZendeskTalkStream - - -class NonIncrementalStream(ZendeskTalkStream): - data_field = "results" - - def path(self, **kwargs) -> str: - return "/test_path" - - -class IncrementalStream(ZendeskTalkIncrementalStream): - data_field = None - cursor_field = "updated_at" - - def path(self, **kwargs) -> str: - return "/test_path" - - -class SingleRecordStream(ZendeskTalkSingleRecordStream): - data_field = "results" - - def path(self, **kwargs) -> str: - return "/test_path" - - -def is_url(url: str) -> bool: - """Checking if provided string is a correct URL, i.e. good enough for urlparse - https://stackoverflow.com/a/52455972/656671 - """ - try: - result = urlparse(url) - return all([result.scheme, result.netloc]) - except ValueError: - return False - - -@pytest.fixture(name="now") -def now_fixture(mocker): - """Fixture to freeze the time""" - return mocker.patch("source_zendesk_talk.streams.pendulum.now", return_value=pendulum.now()) - - -class TestZendeskTalkStream: - def test_url_base(self, mocker): - stream = NonIncrementalStream(subdomain="mydomain", authenticator=mocker.Mock()) - - assert "mydomain" in stream.url_base - assert is_url(stream.url_base), "should be valid URL" - - def test_backoff_time(self, mocker): - stream = NonIncrementalStream(subdomain="mydomain", authenticator=mocker.Mock()) - response = mocker.Mock(spec=requests.Response) - response.headers = {"Retry-After": 10} - - result = stream.backoff_time(response) - - assert result == 10, "should return value from the header if set" - - def test_backoff_time_without_header(self, mocker): - stream = NonIncrementalStream(subdomain="mydomain", authenticator=mocker.Mock()) - response = mocker.Mock(spec=requests.Response) - response.headers = {} - - result = stream.backoff_time(response) - - assert result is None, "no backoff if the header is not set" - - def test_next_page_token(self, mocker): - stream = NonIncrementalStream(subdomain="mydomain", authenticator=mocker.Mock()) - response = mocker.Mock(spec=requests.Response) - response.json.return_value = {"next_page": "https://some.url.com?param1=20¶m2=value"} - - result = stream.next_page_token(response) - - assert result == {"param1": ["20"], "param2": ["value"]}, "should return all params from the next_url" - - def test_next_page_token_end(self, mocker): - stream = NonIncrementalStream(subdomain="mydomain", authenticator=mocker.Mock()) - response = mocker.Mock(spec=requests.Response) - response.json.return_value = {"next_page": None} - - result = stream.next_page_token(response) - - assert result is None, "last page should return no token" - - def test_request_params(self, mocker): - stream = NonIncrementalStream(subdomain="mydomain", authenticator=mocker.Mock()) - - result = stream.request_params(stream_state={}, next_page_token={"some": "token"}) - - assert result == {"some": "token"} - - def test_parse_response(self, mocker): - stream = NonIncrementalStream(subdomain="mydomain", authenticator=mocker.Mock()) - response = mocker.Mock(spec=requests.Response) - response.json.return_value = {stream.data_field: [{"record1"}, {"record2"}, {"record3"}], "some_other_data": 123} - - result = list(stream.parse_response(response=response)) - - assert result == [{"record1"}, {"record2"}, {"record3"}] - - def test_parse_response_from_root(self, mocker): - stream = NonIncrementalStream(subdomain="mydomain", authenticator=mocker.Mock()) - stream.data_field = None - response = mocker.Mock(spec=requests.Response) - response.json.return_value = [{"record1"}, {"record2"}, {"record3"}] - - result = list(stream.parse_response(response=response)) - - assert result == [{"record1"}, {"record2"}, {"record3"}] - - def test_parse_response_single_object(self, mocker): - stream = NonIncrementalStream(subdomain="mydomain", authenticator=mocker.Mock()) - response = mocker.Mock(spec=requests.Response) - response.json.return_value = {stream.data_field: {"record1"}, "some_other_data": 123} - - result = list(stream.parse_response(response=response)) - - assert result == [{"record1"}] - - -class TestZendeskTalkIncrementalStream: - def test_get_updated_state_first_run(self, mocker): - start_date = pendulum.now() - stream = IncrementalStream(subdomain="mydomain", authenticator=mocker.Mock(), start_date=start_date) - current_stream_state = {} - latest_record = {stream.cursor_field: "2020-03-03T01:00:00Z", "some_attr": "value"} - - new_state = stream.get_updated_state(current_stream_state=current_stream_state, latest_record=latest_record) - - assert new_state == {stream.cursor_field: "2020-03-03T01:00:00Z"} - - def test_get_updated_state_desc_order(self, mocker): - start_date = pendulum.now() - stream = IncrementalStream(subdomain="mydomain", authenticator=mocker.Mock(), start_date=start_date) - current_stream_state = {stream.cursor_field: "2020-03-03T02:00:00Z"} - latest_record = {stream.cursor_field: "2020-03-03T01:00:00Z", "some_attr": "value"} - - new_state = stream.get_updated_state(current_stream_state=current_stream_state, latest_record=latest_record) - - assert new_state == {stream.cursor_field: "2020-03-03T02:00:00Z"} - - def test_get_updated_state_legacy_cursor(self, mocker): - start_date = pendulum.now() - stream = IncrementalStream(subdomain="mydomain", authenticator=mocker.Mock(), start_date=start_date) - current_stream_state = {stream.legacy_cursor_field: "2020-03-03T02:00:00Z"} - latest_record = {stream.cursor_field: "2020-03-03T01:00:00Z", "some_attr": "value"} - - new_state = stream.get_updated_state(current_stream_state=current_stream_state, latest_record=latest_record) - - assert new_state == {stream.cursor_field: "2020-03-03T02:00:00Z"} - - def test_get_updated_state(self, mocker): - start_date = pendulum.now() - stream = IncrementalStream(subdomain="mydomain", authenticator=mocker.Mock(), start_date=start_date) - current_stream_state = {stream.cursor_field: "2020-03-03T02:00:00Z"} - latest_record = {stream.cursor_field: "2020-03-03T03:00:00Z", "some_attr": "value"} - - new_state = stream.get_updated_state(current_stream_state=current_stream_state, latest_record=latest_record) - - assert new_state == {stream.cursor_field: "2020-03-03T03:00:00Z"} - - def test_request_params_first_page_without_state(self, mocker): - start_date = pendulum.now() - stream = IncrementalStream(subdomain="mydomain", authenticator=mocker.Mock(), start_date=start_date) - - result = stream.request_params(stream_state={}) - assert result == {stream.filter_param: int(start_date.timestamp())}, "should fallback to start_date" - - def test_request_params_first_page_with_state(self, mocker): - start_date = pendulum.now() - stream = IncrementalStream(subdomain="mydomain", authenticator=mocker.Mock(), start_date=start_date) - - result = stream.request_params(stream_state={stream.cursor_field: "2020-03-03T03:00:00Z"}) - assert result == {stream.filter_param: int(start_date.timestamp())}, "pick always bigger timestamp" - - def test_request_params_pagination(self, mocker): - start_date = pendulum.now() - stream = IncrementalStream(subdomain="mydomain", authenticator=mocker.Mock(), start_date=start_date) - - result = stream.request_params( - stream_state={stream.cursor_field: "2020-03-03T03:00:00Z"}, - next_page_token={stream.filter_param: 12345}, - ) - assert result == {stream.filter_param: 12345}, "page token should always override" - - def test_next_page_token(self, mocker): - start_date = pendulum.now() - stream = IncrementalStream(subdomain="mydomain", authenticator=mocker.Mock(), start_date=start_date) - response = mocker.Mock(spec=requests.Response, request=mocker.Mock(spec=requests.Request)) - response.json.return_value = {"next_page": f"https://some.url.com?param1=20&{stream.filter_param}=value1"} - response.request.url = f"https://some.url.com?param1=30&{stream.filter_param}=value2" - - result = stream.next_page_token(response) - assert result == {"param1": ["20"], stream.filter_param: ["value1"]}, "take page token from next_page" - - def test_next_page_token_empty_response(self, mocker): - start_date = pendulum.now() - stream = IncrementalStream(subdomain="mydomain", authenticator=mocker.Mock(), start_date=start_date) - response = mocker.Mock(spec=requests.Response, request=mocker.Mock(spec=requests.Request)) - response.json.return_value = {"next_page": None} - response.request.url = f"https://some.url.com?param1=30&{stream.filter_param}=value2" - - result = stream.next_page_token(response) - assert result is None, "stop pagination if next page points to the current" - - def test_next_page_token_last_page(self, mocker): - start_date = pendulum.now() - stream = IncrementalStream(subdomain="mydomain", authenticator=mocker.Mock(), start_date=start_date) - response = mocker.Mock(spec=requests.Response, request=mocker.Mock(spec=requests.Request)) - response.json.return_value = {"next_page": f"https://some.url.com?param1=20&{stream.filter_param}=value"} - response.request.url = f"https://some.url.com?param1=30&{stream.filter_param}=value" - - result = stream.next_page_token(response) - assert result is None, "stop pagination if next page points to the current" - - -class TestSingleRecordZendeskTalkStream: - def test_parse_response(self, mocker, now): - stream = SingleRecordStream(subdomain="mydomain", authenticator=mocker.Mock()) - response = mocker.Mock(spec=requests.Response) - response.json.return_value = {stream.data_field: {"field1": "value", "field2": 3}, "some_other_data": 123} - - result = list(stream.parse_response(response=response)) - - assert result == [{"field1": "value", "field2": 3, stream.primary_key: int(now().timestamp())}] - - -class TestIVRMenusStream: - def test_ivr_menus_parse_response(self, mocker): - stream = IVRMenus(subdomain="test-domain", authenticator=mocker.MagicMock()) - ivrs = [ - {"id": random.randint(10000, 99999), "menus": [dict(key="value")]}, - {"id": random.randint(10000, 99999), "menus": [dict(key="value")]}, - {"id": random.randint(10000, 99999), "menus": [dict(key="value")]}, - {"id": random.randint(10000, 99999), "menus": [dict(key="value")]}, - ] - response_data = {"ivrs": ivrs} - response = mocker.MagicMock() - response.json.side_effect = [response_data] - for i, menu in enumerate(stream.parse_response(response)): - assert menu == {"ivr_id": ivrs[i]["id"], **ivrs[i]["menus"][0]} - assert i + 1 == 4 - - -class TestIVRRoutesStream: - def test_ivr_menus_parse_response(self, mocker): - stream = IVRRoutes(subdomain="test-domain", authenticator=mocker.MagicMock()) - ivr_routes = [ - { - "id": 1, - "menus": [ - {"id": 1.1, "routes": [{"route": "1.1.1 route"}, {"route": "1.1.2 route"}]}, - {"id": 1.2, "routes": [{"route": "1.2 route"}]}, - ], - }, - ] - response = mocker.MagicMock() - response.json.side_effect = [{"ivrs": ivr_routes}] - - assert [record for record in stream.parse_response(response)] == [ - {"ivr_id": 1, "ivr_menu_id": 1.1, "id": 1.1, "routes": [{"route": "1.1.1 route"}, {"route": "1.1.2 route"}]}, - {"ivr_id": 1, "ivr_menu_id": 1.1, "id": 1.2, "routes": [{"route": "1.2 route"}]}, - {"ivr_id": 1, "ivr_menu_id": 1.2, "id": 1.1, "routes": [{"route": "1.1.1 route"}, {"route": "1.1.2 route"}]}, - {"ivr_id": 1, "ivr_menu_id": 1.2, "id": 1.2, "routes": [{"route": "1.2 route"}]}, - ] diff --git a/docs/integrations/sources/zendesk-talk-migrations.md b/docs/integrations/sources/zendesk-talk-migrations.md new file mode 100644 index 000000000000..1030881339dd --- /dev/null +++ b/docs/integrations/sources/zendesk-talk-migrations.md @@ -0,0 +1,59 @@ +# Zendesk Talk Migration Guide + +## Upgrading to 1.0.0 + +We're continuously striving to enhance the quality and reliability of our connectors at Airbyte. As part of our commitment to delivering exceptional service, we are transitioning source zendesk-talk from the Python Connector Development Kit (CDK) to our innovative low-code framework. This is part of a strategic move to streamline many processes across connectors, bolstering maintainability and freeing us to focus more of our efforts on improving the performance and features of our evolving platform and growing catalog. However, due to differences between the Python and low-code CDKs, this migration constitutes a breaking change. + +We’ve evolved and standardized how state is managed for incremental streams that are nested within a parent stream. This change impacts how individual states are tracked and stored for each partition, using a more structured approach to ensure the most granular and flexible state management. This change will affect the `calls` and `call_legs` streams. + +To gracefully handle these changes for your existing connections, we highly recommend resetting your data before resuming your data syncs with the new version. + +## Migration Steps + +### For Airbyte Open Source: Update the local connector image + +Airbyte Open Source users must manually update the connector image in their local registry before proceeding with the migration. To do so: + +1. Select **Settings** in the main navbar. + 1. Select **Sources**. +2. Find Zendesk Talk in the list of connectors. + +:::note +You will see two versions listed, the current in-use version and the latest version available. +::: + +3. Select **Change** to update your OSS version to the latest available version. + +### Update the connector version + +1. Select **Sources** in the main navbar. +2. Select the instance of the connector you wish to upgrade. + +:::note +Each instance of the connector must be updated separately. If you have created multiple instances of a connector, updating one will not affect the others. +::: + +3. Select **Upgrade** + 1. Follow the prompt to confirm you are ready to upgrade to the new version. + +### Refresh affected schemas and reset data + +1. Select **Connections** in the main nav bar. + 1. Select the connection(s) affected by the update. +2. Select the **Replication** tab. + 1. Select **Refresh source schema**. + 2. Select **OK**. +:::note +Any detected schema changes will be listed for your review. +::: +3. Select **Save changes** at the bottom of the page. + 1. Ensure the **Reset affected streams** option is checked. +:::note +Depending on destination type you may not be prompted to reset your data. +::: +4. Select **Save connection**. +:::note +This will reset the data in your destination and initiate a fresh sync. +::: + +For more information on resetting your data in Airbyte, see [this page](https://docs.airbyte.com/operator-guides/reset). diff --git a/docs/integrations/sources/zendesk-talk.md b/docs/integrations/sources/zendesk-talk.md index bb0e6aa3d38b..76b7f9552e2b 100644 --- a/docs/integrations/sources/zendesk-talk.md +++ b/docs/integrations/sources/zendesk-talk.md @@ -65,17 +65,18 @@ The Zendesk connector should not run into Zendesk API limitations under normal u ## Data type map -| Integration Type | Airbyte Type | Notes | -| :--------------- | :----------- | :---- | -| `string` | `string` | | -| `number` | `number` | | -| `array` | `array` | | -| `object` | `object` | | +| Integration Type | Airbyte Type | +|:-----------------|:-------------| +| `string` | `string` | +| `number` | `number` | +| `array` | `array` | +| `object` | `object` | ## Changelog | Version | Date | Pull Request | Subject | -| :------ | :--------- | :------------------------------------------------------- | :-------------------------------------------------------------------------- | +|:--------|:-----------|:---------------------------------------------------------|:----------------------------------------------------------------------------| +| 1.0.0 | 2024-05-06 | [35780](https://github.com/airbytehq/airbyte/pull/35780) | Migrate implementation to low-code CDK | | 0.2.1 | 2024-05-02 | [36625](https://github.com/airbytehq/airbyte/pull/36625) | Schema descriptions and CDK 0.80.0 | | 0.2.0 | 2024-03-25 | [36459](https://github.com/airbytehq/airbyte/pull/36459) | Unpin CDK version, add record counts in state messages | | 0.1.13 | 2024-03-04 | [35783](https://github.com/airbytehq/airbyte/pull/35783) | Change order of authentication methods in spec |