diff --git a/airbyte-integrations/connectors/source-pinterest/acceptance-test-config.yml b/airbyte-integrations/connectors/source-pinterest/acceptance-test-config.yml index ab97ad555c2d..768d1d550502 100644 --- a/airbyte-integrations/connectors/source-pinterest/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-pinterest/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_oauth.json status: succeed discovery: @@ -23,26 +23,24 @@ acceptance_tests: tests: - config_path: secrets/config.json empty_streams: - - bypass_reason: The stream could return 0 records, because of low rate-limits - name: ad_account_analytics - - bypass_reason: The stream could return 0 records, because of low rate-limits - name: ad_analytics - - bypass_reason: The stream could return 0 records, because of low rate-limits - name: ad_group_analytics - - bypass_reason: The stream could return 0 records, because of low rate-limits - name: ad_groups - - bypass_reason: The stream could return 0 records, because of low rate-limits - name: ads - - bypass_reason: The stream could return 0 records, because of low rate-limits - name: board_section_pins - - bypass_reason: The stream could return 0 records, because of low rate-limits - name: board_sections - - bypass_reason: The stream could return 0 records, because of low rate-limits - name: campaign_analytics - - bypass_reason: The stream could return 0 records, because of low rate-limits - name: campaigns - - bypass_reason: The stream could return 0 records, because of low rate-limits - name: user_account_analytics + - name: conversion_tags + bypass_reason: Not possible to add data + - name: customer_lists + bypass_reason: Not possible to add data + - name: catalogs + bypass_reason: Not possible to add data + - name: catalogs_feeds + bypass_reason: Not possible to add data + - name: catalogs_product_groups + bypass_reason: Not possible to add data + - name: product_group_report + bypass_reason: Not possible to add data + - name: product_group_targeting_report + bypass_reason: Not possible to add data + - name: product_item_report + bypass_reason: Not possible to add data + - name: keyword_report + bypass_reason: Not possible to add data timeout_seconds: 1200 expect_records: path: "integration_tests/expected_records.jsonl" @@ -52,8 +50,27 @@ acceptance_tests: fail_on_extra_columns: false ignored_fields: board_pins: - - name: "links" - bypass_reason: "because it contains non-secure http link, which leads to failed QA tests" + - name: "media" + bypass_reason: "urls may change" + board_section_pins: + - name: "media" + bypass_reason: "urls may change" + ads: + - name: "updated_time" + bypass_reason: "can be updated" + ad_groups: + - name: "updated_time" + bypass_reason: "can be updated" + campaigns: + - name: "updated_time" + bypass_reason: "can be updated" + audiences: + - name: "size" + bypass_reason: "can be changed" + - name: "updated_timestamp" + bypass_reason: "can be changed" + - name: "created_timestamp" + bypass_reason: "can be changed" incremental: tests: - config_path: secrets/config.json diff --git a/airbyte-integrations/connectors/source-pinterest/integration_tests/expected_records.jsonl b/airbyte-integrations/connectors/source-pinterest/integration_tests/expected_records.jsonl index 1215f423eb58..dbba2254d507 100644 --- a/airbyte-integrations/connectors/source-pinterest/integration_tests/expected_records.jsonl +++ b/airbyte-integrations/connectors/source-pinterest/integration_tests/expected_records.jsonl @@ -1,4 +1,23 @@ {"stream": "ad_accounts", "data": {"id": "549761668032", "name": "Airbyte", "owner": {"username": "integrationtest0375", "id": "666744057242074926"}, "country": "US", "currency": "USD", "permissions": ["OWNER"], "created_time": 1603772920, "updated_time": 1623173784}, "emitted_at": 1688461289470} +{"stream": "ad_account_analytics", "data": {"TOTAL_IMPRESSION_FREQUENCY": 1.0, "TOTAL_IMPRESSION_USER": 1.0, "ADVERTISER_ID": "549761668032", "DATE": "2023-10-29", "IMPRESSION_2": 1.0, "AD_ACCOUNT_ID": "549761668032"}, "emitted_at": 1699893121669} +{"stream": "ads", "data": {"id": "687218400118", "ad_group_id": "2680068678965", "ad_account_id": "549761668032", "android_deep_link": null, "campaign_id": "626744128956", "carousel_android_deep_links": null, "carousel_destination_urls": null, "carousel_ios_deep_links": null, "click_tracking_url": null, "collection_items_destination_url_template": null, "created_time": 1623245885, "creative_type": "REGULAR", "destination_url": "https://airbyte.io/", "ios_deep_link": null, "is_pin_deleted": false, "is_removable": false, "name": "2021-06-09 | Traffic | Keywords | Data Integration", "pin_id": "666743919837294988", "rejected_reasons": [], "rejection_labels": [], "review_status": "APPROVED", "status": "PAUSED", "summary_status": "PAUSED", "tracking_urls": null, "type": "ad", "updated_time": 1699373013, "view_tracking_url": null, "lead_form_id": null}, "emitted_at": 1699393433303} +{"stream": "ad_analytics", "data": {"PIN_ID": 6.66743919837295e+17, "AD_GROUP_ID": "2680068678993", "AD_GROUP_ENTITY_STATUS": "1", "CAMPAIGN_ENTITY_STATUS": 1.0, "TOTAL_IMPRESSION_FREQUENCY": 1.0, "CAMPAIGN_LIFETIME_SPEND_CAP": 0.0, "TOTAL_IMPRESSION_USER": 1.0, "CAMPAIGN_DAILY_SPEND_CAP": 25000000.0, "AD_ID": "687218400210", "ADVERTISER_ID": "549761668032", "PIN_PROMOTION_ID": 687218400210.0, "DATE": "2023-10-29", "IMPRESSION_2": 1.0, "AD_ACCOUNT_ID": "549761668032", "CAMPAIGN_ID": 626744128982.0, "CAMPAIGN_NAME": "2021-06-08 09:08 UTC | Brand awareness"}, "emitted_at": 1699893196846} +{"stream": "ad_groups", "data": {"id": "2680068678965", "created_time": 1623245885.0, "updated_time": 1699373013.0, "start_time": null, "end_time": null, "bid_in_micro_currency": null, "budget_in_micro_currency": null, "campaign_id": "626744128956", "ad_account_id": "549761668032", "auto_targeting_enabled": true, "type": "adgroup", "budget_type": "CBO_ADGROUP", "billable_event": "CLICKTHROUGH", "status": "ACTIVE", "lifetime_frequency_cap": -1.0, "targeting_spec": {"GENDER": ["female", "male", "unknown"], "APPTYPE": ["web", "web_mobile", "iphone", "ipad", "android_mobile", "android_tablet"], "LOCALE": ["cs", "da", "de", "el", "en", "es", "fi", "fr", "hu", "id", "it", "ja", "ko", "nb", "nl", "pl", "pt", "ro", "ru", "sk", "sv", "tr", "uk", "zh"], "TARGETING_STRATEGY": ["CHOOSE_YOUR_OWN"], "LOCATION": ["US"]}, "name": "2021-06-09 | Traffic | Keywords | Data Integration", "placement_group": "ALL", "pacing_delivery_type": "STANDARD", "tracking_urls": null, "conversion_learning_mode_type": null, "summary_status": "COMPLETED", "feed_profile_id": "0", "placement_traffic_type": null, "optimization_goal_metadata": {}, "bid_strategy_type": "AUTOMATIC_BID"}, "emitted_at": 1699393433712} +{"stream": "ad_group_analytics", "data": {"AD_GROUP_ID": "2680068678993", "AD_GROUP_ENTITY_STATUS": "1", "CAMPAIGN_ENTITY_STATUS": 1.0, "TOTAL_IMPRESSION_FREQUENCY": 1.0, "CAMPAIGN_LIFETIME_SPEND_CAP": 0.0, "TOTAL_IMPRESSION_USER": 1.0, "CAMPAIGN_DAILY_SPEND_CAP": 25000000.0, "ADVERTISER_ID": "549761668032", "DATE": "2023-10-29", "IMPRESSION_2": 1.0, "AD_ACCOUNT_ID": "549761668032", "CAMPAIGN_ID": 626744128982.0, "CAMPAIGN_NAME": "2021-06-08 09:08 UTC | Brand awareness"}, "emitted_at": 1699893280169} {"stream": "boards", "data": {"media": {"pin_thumbnail_urls": [], "image_cover_url": "https://i.pinimg.com/400x300/c6/b6/0d/c6b60d6b5f2ec04db7748d35fb1a8004.jpg"}, "owner": {"username": "integrationtest0375"}, "created_at": "2021-06-08T09:37:18", "board_pins_modified_at": "2021-10-25T11:17:56.715000", "id": "666743988523388559", "collaborator_count": 0, "follower_count": 2, "pin_count": 1, "privacy": "PUBLIC", "name": "business", "description": ""}, "emitted_at": 1680356853019} -{"stream": "board_pins", "data": {"description": "Data Integration", "board_owner": {"username": "integrationtest0375"}, "product_tags": [], "has_been_promoted": true, "created_at": "2021-06-08T09:37:30", "board_id": "666743988523388559", "note": "", "creative_type": "REGULAR", "parent_pin_id": null, "title": "Airbyte", "alt_text": null, "pin_metrics": null, "dominant_color": "#cacafe", "id": "666743919837294988", "is_owner": true, "board_section_id": "5195034916661798218", "link": "http://airbyte.io/", "media": {"media_type": "image", "images": {"150x150": {"width": 150, "height": 150, "url": "https://i.pinimg.com/150x150/c6/b6/0d/c6b60d6b5f2ec04db7748d35fb1a8004.jpg"}, "400x300": {"width": 400, "height": 300, "url": "https://i.pinimg.com/400x300/c6/b6/0d/c6b60d6b5f2ec04db7748d35fb1a8004.jpg"}, "600x": {"width": 564, "height": 337, "url": "https://i.pinimg.com/564x/c6/b6/0d/c6b60d6b5f2ec04db7748d35fb1a8004.jpg"}, "1200x": {"width": 1200, "height": 718, "url": "https://i.pinimg.com/1200x/c6/b6/0d/c6b60d6b5f2ec04db7748d35fb1a8004.jpg"}}}, "is_standard": true}, "emitted_at": 1698398201666} -{"stream": "campaign_analytics_report", "data": {"ADVERTISER_ID": 549761668032.0, "AD_ACCOUNT_ID": "549761668032", "CAMPAIGN_DAILY_SPEND_CAP": 750000.0, "CAMPAIGN_ENTITY_STATUS": "ACTIVE", "CAMPAIGN_ID": 626744128982.0, "CAMPAIGN_LIFETIME_SPEND_CAP": 0.0, "CAMPAIGN_NAME": "2021-06-08 09:08 UTC | Brand awareness", "IMPRESSION_2": 3.0, "TOTAL_IMPRESSION_FREQUENCY": 1.5, "TOTAL_IMPRESSION_USER": 2.0, "DATE": "2023-07-14"}, "emitted_at": 1690299367301} +{"stream": "board_pins", "data": {"description": "Data Integration", "board_owner": {"username": "integrationtest0375"}, "product_tags": [], "has_been_promoted": true,"link":"http://airbyte.io/", "created_at": "2021-06-08T09:37:30", "board_id": "666743988523388559", "note": "", "creative_type": "REGULAR", "parent_pin_id": null, "title": "Airbyte", "alt_text": null, "pin_metrics": null, "dominant_color": "#cacafe", "id": "666743919837294988", "is_owner": true, "board_section_id": "5195034916661798218", "is_standard": true}, "emitted_at": 1698398201666} +{"stream": "board_sections", "data": {"name": "Airbyte_board_section_new", "id": "5195035116725909603"}, "emitted_at": 1699893323493} +{"stream": "board_section_pins","data":{"id":"666743919837294988","dominant_color":"#cacafe","pin_metrics":null,"title":"Airbyte","creative_type":"REGULAR","link":"http://airbyte.io/","board_id":"666743988523388559","created_at":"2021-06-08T09:37:30","is_owner":true,"description":"Data Integration","note":"","alt_text":null,"board_section_id":"5195034916661798218","parent_pin_id":null,"product_tags":[],"board_owner":{"username":"integrationtest0375"},"is_standard":true,"has_been_promoted":true},"emitted_at":1699893364884} +{"stream": "campaigns", "data": {"id": "626744128956", "ad_account_id": "549761668032", "name": "2021-06-09 | Traffic | Keywords | Data Integration", "status": "ACTIVE", "objective_type": "CONSIDERATION", "lifetime_spend_cap": 0, "daily_spend_cap": 3000000, "order_line_id": null, "tracking_urls": null, "created_time": 1623245885, "updated_time": 1691447502, "type": "campaign", "is_flexible_daily_budgets": false, "summary_status": "COMPLETED", "is_campaign_budget_optimization": true, "start_time": 1623196800, "end_time": 1624060800}, "emitted_at": 1699393571700} +{"stream": "campaign_analytics", "data": {"TOTAL_IMPRESSION_FREQUENCY": 1.0, "CAMPAIGN_LIFETIME_SPEND_CAP": 0.0, "TOTAL_IMPRESSION_USER": 1.0, "CAMPAIGN_ENTITY_STATUS": 1.0, "CAMPAIGN_DAILY_SPEND_CAP": 25000000.0, "ADVERTISER_ID": 549761668032.0, "DATE": "2023-10-29", "IMPRESSION_2": 1.0, "AD_ACCOUNT_ID": "549761668032", "CAMPAIGN_ID": 626744128982.0, "CAMPAIGN_NAME": "2021-06-08 09:08 UTC | Brand awareness"}, "emitted_at": 1699894065462} +{"stream": "campaign_analytics_report", "data": {"ADVERTISER_ID": 549761668032.0, "AD_ACCOUNT_ID": "549761668032", "CAMPAIGN_DAILY_SPEND_CAP": 25000000.0, "CAMPAIGN_ENTITY_STATUS": "ACTIVE", "CAMPAIGN_ID": 626744128982.0, "CAMPAIGN_LIFETIME_SPEND_CAP": 0.0, "CAMPAIGN_NAME": "2021-06-08 09:08 UTC | Brand awareness", "IMPRESSION_2": 3.0, "TOTAL_IMPRESSION_FREQUENCY": 1.5, "TOTAL_IMPRESSION_USER": 2.0, "DATE": "2023-07-14"}, "emitted_at": 1690299367301} +{"stream": "campaign_targeting_report", "data": {"ADVERTISER_ID": 549761668032.0, "AD_ACCOUNT_ID": "549761668032", "CAMPAIGN_DAILY_SPEND_CAP": 25000000.0, "CAMPAIGN_ENTITY_STATUS": "ACTIVE", "CAMPAIGN_ID": 626744128982.0, "CAMPAIGN_LIFETIME_SPEND_CAP": 0.0, "CAMPAIGN_NAME": "2021-06-08 09:08 UTC | Brand awareness", "IMPRESSION_2": 1.0, "TARGETING_VALUE": "TWOCOLUMN_FEED", "TARGETING_TYPE": "FEED_TYPE", "DATE": "2023-10-29"}, "emitted_at": 1699894287823} +{"stream": "user_account_analytics", "data": {"date": "2023-11-09", "data_status": "READY", "metrics": {"SAVE": 2.0, "OUTBOUND_CLICK_RATE": 0.0043859649122807015, "IMPRESSION": 912.0, "VIDEO_START": 0, "SAVE_RATE": 0.0021929824561403508, "QUARTILE_95_PERCENT_VIEW": 0, "ENGAGEMENT": 22.0, "VIDEO_AVG_WATCH_TIME": 0.0, "ENGAGEMENT_RATE": 0.02412280701754386, "PIN_CLICK": 17, "VIDEO_10S_VIEW": 0, "FULL_SCREEN_PLAY": 0, "CLOSEUP_RATE": 0.017543859649122806, "FULL_SCREEN_PLAYTIME": 0, "VIDEO_V50_WATCH_TIME": 0, "VIDEO_MRC_VIEW": 0, "CLICKTHROUGH": 4.0, "CLICKTHROUGH_RATE": 0.0043859649122807015, "OUTBOUND_CLICK": 4, "CLOSEUP": 16.0, "PIN_CLICK_RATE": 0.01864035087719298}}, "emitted_at": 1699894362486} +{"stream": "keywords", "data": {"archived": false, "id": "2886935172273", "parent_id": "2680068678965", "parent_type": "AD_GROUP", "type": "KEYWORD", "bid": null, "match_type": "BROAD", "value": "data science"}, "emitted_at": 1699393669235} +{"stream": "audiences", "data": {"type": "audience", "id": "2542622254639", "name": "airbyte audience", "ad_account_id": "549761668032", "audience_type": "ENGAGEMENT", "description": "airbyte audience", "status": "TOO_SMALL", "rule": {"engager_type": 1}}, "emitted_at": 1699293090886} +{"stream": "advertizer_report", "data": {"ADVERTISER_ID": 549761668032.0, "AD_ACCOUNT_ID": "549761668032", "EENGAGEMENT_RATE": 0.1, "ENGAGEMENT_2": 1.0, "IMPRESSION_2": 10.0, "REPIN_2": 1.0, "TOTAL_ENGAGEMENT": 1.0, "TOTAL_IMPRESSION_FREQUENCY": 5.0, "TOTAL_IMPRESSION_USER": 2.0, "TOTAL_REPIN_RATE": 0.1, "DATE": "2023-02-10"}, "emitted_at": 1699894848024} +{"stream": "advertizer_targeting_report", "data": {"ADVERTISER_ID": 549761668032.0, "AD_ACCOUNT_ID": "549761668032", "IMPRESSION_2": 1.0, "TARGETING_VALUE": "Education > Subjects > Science > Applied Science > Technology", "TARGETING_TYPE": "TARGETED_INTEREST", "DATE": "2023-10-29"}, "emitted_at": 1699894982269} +{"stream": "ad_group_report", "data": {"ADVERTISER_ID": 549761668032.0, "AD_ACCOUNT_ID": "549761668032", "AD_GROUP_ENTITY_STATUS": "ACTIVE", "AD_GROUP_ID": "2680068678993", "CAMPAIGN_DAILY_SPEND_CAP": 25000000.0, "CAMPAIGN_ENTITY_STATUS": "ACTIVE", "CAMPAIGN_ID": 626744128982.0, "CAMPAIGN_LIFETIME_SPEND_CAP": 0.0, "CAMPAIGN_NAME": "2021-06-08 09:08 UTC | Brand awareness", "IMPRESSION_2": 1.0, "TOTAL_IMPRESSION_FREQUENCY": 1.0, "TOTAL_IMPRESSION_USER": 1.0, "DATE": "2023-10-29"}, "emitted_at": 1699895043538} +{"stream": "ad_group_targeting_report", "data": {"ADVERTISER_ID": 549761668032.0, "AD_ACCOUNT_ID": "549761668032", "AD_GROUP_ENTITY_STATUS": "ACTIVE", "AD_GROUP_ID": "2680068678993", "CAMPAIGN_DAILY_SPEND_CAP": 25000000.0, "CAMPAIGN_ENTITY_STATUS": "ACTIVE", "CAMPAIGN_ID": 626744128982.0, "CAMPAIGN_LIFETIME_SPEND_CAP": 0.0, "CAMPAIGN_NAME": "2021-06-08 09:08 UTC | Brand awareness", "IMPRESSION_2": 1.0, "TARGETING_VALUE": "TWOCOLUMN_FEED", "TARGETING_TYPE": "FEED_TYPE", "DATE": "2023-10-29"}, "emitted_at": 1699895106949} +{"stream": "pin_promotion_report", "data": {"ADVERTISER_ID": 549761668032.0, "AD_ACCOUNT_ID": "549761668032", "AD_GROUP_ENTITY_STATUS": "ACTIVE", "AD_GROUP_ID": "2680068678993", "AD_ID": "687218400210", "CAMPAIGN_DAILY_SPEND_CAP": 25000000.0, "CAMPAIGN_ENTITY_STATUS": "ACTIVE", "CAMPAIGN_ID": 626744128982.0, "CAMPAIGN_LIFETIME_SPEND_CAP": 0.0, "CAMPAIGN_NAME": "2021-06-08 09:08 UTC | Brand awareness", "IMPRESSION_2": 1.0, "PIN_ID": 6.66743919837295e+17, "PIN_PROMOTION_ID": 687218400210.0, "TOTAL_IMPRESSION_FREQUENCY": 1.0, "TOTAL_IMPRESSION_USER": 1.0, "DATE": "2023-10-29"}, "emitted_at": 1699895200157} +{"stream": "pin_promotion_targeting_report", "data": {"ADVERTISER_ID": 549761668032.0, "AD_ACCOUNT_ID": "549761668032", "AD_GROUP_ENTITY_STATUS": "ACTIVE", "AD_GROUP_ID": "2680068678993", "AD_ID": "687218400210", "CAMPAIGN_DAILY_SPEND_CAP": 25000000.0, "CAMPAIGN_ENTITY_STATUS": "ACTIVE", "CAMPAIGN_ID": 626744128982.0, "CAMPAIGN_LIFETIME_SPEND_CAP": 0.0, "CAMPAIGN_NAME": "2021-06-08 09:08 UTC | Brand awareness", "IMPRESSION_2": 1.0, "PIN_ID": 6.66743919837295e+17, "PIN_PROMOTION_ID": 687218400210.0, "TARGETING_VALUE": "Education > Subjects > Science > Applied Science > Technology", "TARGETING_TYPE": "TARGETED_INTEREST", "DATE": "2023-10-29"}, "emitted_at": 1699895289749} \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-pinterest/metadata.yaml b/airbyte-integrations/connectors/source-pinterest/metadata.yaml index 5ce3b8a4ca19..4763dd34d13a 100644 --- a/airbyte-integrations/connectors/source-pinterest/metadata.yaml +++ b/airbyte-integrations/connectors/source-pinterest/metadata.yaml @@ -5,7 +5,7 @@ data: connectorSubtype: api connectorType: source definitionId: 5cb7e5fe-38c2-11ec-8d3d-0242ac130003 - dockerImageTag: 0.7.1 + dockerImageTag: 0.7.2 dockerRepository: airbyte/source-pinterest connectorBuildOptions: baseImage: docker.io/airbyte/python-connector-base:1.1.0@sha256:bd98f6505c6764b1b5f99d3aedc23dfc9e9af631a62533f60eb32b1d3dbab20c diff --git a/airbyte-integrations/connectors/source-pinterest/source_pinterest/reports/reports.py b/airbyte-integrations/connectors/source-pinterest/source_pinterest/reports/reports.py index fcf5437cedd6..98e4809bb296 100644 --- a/airbyte-integrations/connectors/source-pinterest/source_pinterest/reports/reports.py +++ b/airbyte-integrations/connectors/source-pinterest/source_pinterest/reports/reports.py @@ -4,12 +4,15 @@ import json from abc import abstractmethod +from functools import lru_cache from typing import Any, Iterable, List, Mapping, MutableMapping, Optional from urllib.parse import urljoin import backoff import requests from airbyte_cdk.models import SyncMode +from airbyte_cdk.sources.streams.core import package_name_from_class +from airbyte_cdk.sources.utils.schema_helpers import ResourceSchemaLoader from source_pinterest.streams import PinterestAnalyticsStream from source_pinterest.utils import get_analytics_columns @@ -164,8 +167,96 @@ def _fetch_report_data(self, url: str) -> dict: """Fetch the report data from the given URL.""" return self._http_get(url) + @lru_cache(maxsize=None) + def get_json_schema(self) -> Mapping[str, Any]: + """ + :return: A dict of the JSON schema representing this stream. + + Schema is the same for all *Report and *TargetingReport streams + """ + + return ResourceSchemaLoader(package_name_from_class(self.__class__)).get_schema("reports") + + +class PinterestAnalyticsTargetingReportStream(PinterestAnalyticsReportStream): + def request_body_json(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> Optional[Mapping]: + """Return the body of the API request in JSON format.""" + columns = get_analytics_columns().split(",") + # remove keys which are lot suitable for targeting report + for odd_value in ["TOTAL_IMPRESSION_FREQUENCY", "TOTAL_IMPRESSION_USER"]: + if odd_value in columns: + columns.remove(odd_value) + columns = ",".join(columns) + return self._construct_request_body(stream_slice["start_date"], stream_slice["end_date"], self.granularity, columns) + class CampaignAnalyticsReport(PinterestAnalyticsReportStream): @property def level(self): return "CAMPAIGN" + + +class CampaignTargetingReport(PinterestAnalyticsTargetingReportStream): + @property + def level(self): + return "CAMPAIGN_TARGETING" + + +class AdvertizerReport(PinterestAnalyticsReportStream): + @property + def level(self): + return "ADVERTISER" + + +class AdvertizerTargetingReport(PinterestAnalyticsTargetingReportStream): + @property + def level(self): + return "ADVERTISER_TARGETING" + + +class AdGroupReport(PinterestAnalyticsReportStream): + @property + def level(self): + return "AD_GROUP" + + +class AdGroupTargetingReport(PinterestAnalyticsTargetingReportStream): + @property + def level(self): + return "AD_GROUP_TARGETING" + + +class PinPromotionReport(PinterestAnalyticsReportStream): + @property + def level(self): + return "PIN_PROMOTION" + + +class PinPromotionTargetingReport(PinterestAnalyticsTargetingReportStream): + @property + def level(self): + return "PIN_PROMOTION_TARGETING" + + +class ProductGroupReport(PinterestAnalyticsReportStream): + @property + def level(self): + return "PRODUCT_GROUP" + + +class ProductGroupTargetingReport(PinterestAnalyticsTargetingReportStream): + @property + def level(self): + return "PRODUCT_GROUP_TARGETING" + + +class ProductItemReport(PinterestAnalyticsReportStream): + @property + def level(self): + return "PRODUCT_ITEM" + + +class KeywordReport(PinterestAnalyticsTargetingReportStream): + @property + def level(self): + return "KEYWORD" diff --git a/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/audiences.json b/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/audiences.json new file mode 100644 index 000000000000..2ccb1cafad02 --- /dev/null +++ b/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/audiences.json @@ -0,0 +1,75 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "ad_account_id": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "audience_type": { + "type": ["null", "string"] + }, + "description": { + "type": ["null", "string"] + }, + "rule": { + "type": ["null", "object"], + "properties": { + "country": { + "type": ["null", "string"] + }, + "customer_list_id": { + "type": ["null", "string"] + }, + "engagement_domain": { + "type": ["null", "array"], + "items": {} + }, + "engagement_type": { + "type": ["null", "string"] + }, + "event": { + "type": ["null", "string"] + }, + "percentage": { + "type": ["null", "integer"] + }, + "prefill": { + "type": ["null", "boolean"] + }, + "retention_days": { + "type": ["null", "integer"] + }, + "visitor_source_id": { + "type": ["null", "string"] + }, + "engager_type": { + "type": ["null", "integer"] + }, + "ad_account_id": { + "type": ["null", "string"] + } + } + }, + "size": { + "type": ["null", "integer"] + }, + "status": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "created_timestamp": { + "type": ["null", "integer"] + }, + "updated_timestamp": { + "type": ["null", "integer"] + } + } +} diff --git a/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/board_pins.json b/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/board_pins.json index a91140d63b3b..2989e890ced2 100644 --- a/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/board_pins.json +++ b/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/board_pins.json @@ -7,7 +7,8 @@ "type": ["null", "string"] }, "created_at": { - "type": ["null", "string"] + "type": ["null", "string"], + "format": "date-time" }, "creative_type": { "type": ["null", "string"], diff --git a/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/board_section_pins.json b/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/board_section_pins.json index c19b701e35b7..74bdf144cb8a 100644 --- a/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/board_section_pins.json +++ b/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/board_section_pins.json @@ -6,7 +6,8 @@ "type": ["null", "string"] }, "created_at": { - "type": ["null", "string"] + "type": ["null", "string"], + "format": "date-time" }, "link": { "type": ["null", "string"] diff --git a/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/catalogs.json b/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/catalogs.json new file mode 100644 index 000000000000..bf3a1bf06b12 --- /dev/null +++ b/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/catalogs.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "id": { + "type": ["null", "string"] + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "name": { + "type": ["null", "string"] + }, + "catalog_type": { + "type": ["null", "string"] + } + } +} diff --git a/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/catalogs_feeds.json b/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/catalogs_feeds.json new file mode 100644 index 000000000000..2474d6bf723e --- /dev/null +++ b/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/catalogs_feeds.json @@ -0,0 +1,55 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "id": { + "type": ["null", "string"] + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "name": { + "type": ["null", "string"] + }, + "format": { + "type": ["null", "string"] + }, + "catalog_type": { + "type": ["null", "string"] + }, + "location": { + "type": ["null", "string"] + }, + "preferred_processing_schedule": { + "type": ["null", "object"], + "properties": { + "time": { + "type": ["null", "string"] + }, + "timezone": { + "type": ["null", "string"] + } + } + }, + "status": { + "type": ["null", "string"] + }, + "default_currency": { + "type": ["null", "string"] + }, + "default_locale": { + "type": ["null", "string"] + }, + "default_country": { + "type": ["null", "string"] + }, + "default_availability": { + "type": ["null", "string"] + } + } +} diff --git a/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/catalogs_product_groups.json b/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/catalogs_product_groups.json new file mode 100644 index 000000000000..0d627d1bf1c0 --- /dev/null +++ b/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/catalogs_product_groups.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "created_at": { + "type": ["null", "integer"] + }, + "description": { + "type": ["null", "string"] + }, + "feed_id": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + }, + "is_featured": { + "type": ["null", "boolean"] + }, + "name": { + "type": ["null", "string"] + }, + "status": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "updated_at": { + "type": ["null", "integer"] + } + } +} diff --git a/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/conversion_tags.json b/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/conversion_tags.json new file mode 100644 index 000000000000..aa218ebd0bcd --- /dev/null +++ b/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/conversion_tags.json @@ -0,0 +1,56 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "ad_account_id": { + "type": ["null", "string"] + }, + "code_snippet": { + "type": ["null", "string"] + }, + "enhanced_match_status": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + }, + "last_fired_time_ms": { + "type": ["null", "integer"] + }, + "name": { + "type": ["null", "string"] + }, + "status": { + "type": ["null", "string"] + }, + "version": { + "type": ["null", "string"] + }, + "configs": { + "type": ["null", "object"], + "properties": { + "aem_enabled": { + "type": ["null", "boolean"] + }, + "md_frequency": { + "type": ["null", "number"] + }, + "aem_fnln_enabled": { + "type": ["null", "boolean"] + }, + "aem_ph_enabled": { + "type": ["null", "boolean"] + }, + "aem_ge_enabled": { + "type": ["null", "boolean"] + }, + "aem_db_enabled": { + "type": ["null", "boolean"] + }, + "aem_loc_enabled": { + "type": ["null", "boolean"] + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/customer_lists.json b/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/customer_lists.json new file mode 100644 index 000000000000..551d18c2fd8b --- /dev/null +++ b/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/customer_lists.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "ad_account_id": { + "type": ["null", "string"] + }, + "created_time": { + "type": ["null", "integer"] + }, + "id": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "num_batches": { + "type": ["null", "integer"] + }, + "num_removed_user_records": { + "type": ["null", "integer"] + }, + "num_uploaded_user_records": { + "type": ["null", "integer"] + }, + "status": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "updated_time": { + "type": ["null", "integer"] + } + } +} diff --git a/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/keywords.json b/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/keywords.json new file mode 100644 index 000000000000..8057db2ce275 --- /dev/null +++ b/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/keywords.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "archived": { + "type": ["null", "boolean"] + }, + "id": { + "type": ["null", "string"] + }, + "parent_id": { + "type": ["null", "string"] + }, + "parent_type": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "bid": { + "type": ["null", "integer"] + }, + "match_type": { + "type": ["null", "string"] + }, + "value": { + "type": ["null", "string"] + } + } +} diff --git a/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/reports.json b/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/reports.json new file mode 100644 index 000000000000..c41983853652 --- /dev/null +++ b/airbyte-integrations/connectors/source-pinterest/source_pinterest/schemas/reports.json @@ -0,0 +1,346 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "DATE": { + "type": ["null", "string"], + "format": "date" + }, + "ADVERTISER_ID": { + "type": ["null", "number"] + }, + "AD_ACCOUNT_ID": { + "type": ["string"] + }, + "AD_ID": { + "type": ["null", "string"] + }, + "AD_GROUP_ENTITY_STATUS": { + "type": ["null", "string"] + }, + "AD_GROUP_ID": { + "type": ["null", "string"] + }, + "CAMPAIGN_DAILY_SPEND_CAP": { + "type": ["null", "number"] + }, + "CAMPAIGN_ENTITY_STATUS": { + "type": ["null", "string"] + }, + "CAMPAIGN_ID": { + "type": ["null", "number"] + }, + "CAMPAIGN_LIFETIME_SPEND_CAP": { + "type": ["null", "number"] + }, + "CAMPAIGN_NAME": { + "type": ["null", "string"] + }, + "CHECKOUT_ROAS": { + "type": ["null", "number"] + }, + "CLICKTHROUGH_1": { + "type": ["null", "number"] + }, + "CLICKTHROUGH_1_GROSS": { + "type": ["null", "number"] + }, + "CLICKTHROUGH_2": { + "type": ["null", "number"] + }, + "CPC_IN_MICRO_DOLLAR": { + "type": ["null", "number"] + }, + "CPM_IN_DOLLAR": { + "type": ["null", "number"] + }, + "CPM_IN_MICRO_DOLLAR": { + "type": ["null", "number"] + }, + "CTR": { + "type": ["null", "number"] + }, + "CTR_2": { + "type": ["null", "number"] + }, + "ECPCV_IN_DOLLAR": { + "type": ["null", "number"] + }, + "ECPCV_P95_IN_DOLLAR": { + "type": ["null", "number"] + }, + "ECPC_IN_DOLLAR": { + "type": ["null", "number"] + }, + "ECPC_IN_MICRO_DOLLAR": { + "type": ["null", "number"] + }, + "ECPE_IN_DOLLAR": { + "type": ["null", "number"] + }, + "ECPM_IN_MICRO_DOLLAR": { + "type": ["null", "number"] + }, + "ECPV_IN_DOLLAR": { + "type": ["null", "number"] + }, + "ECTR": { + "type": ["null", "number"] + }, + "EENGAGEMENT_RATE": { + "type": ["null", "number"] + }, + "ENGAGEMENT_1": { + "type": ["null", "number"] + }, + "ENGAGEMENT_2": { + "type": ["null", "number"] + }, + "ENGAGEMENT_RATE": { + "type": ["null", "number"] + }, + "IDEA_PIN_PRODUCT_TAG_VISIT_1": { + "type": ["null", "number"] + }, + "IDEA_PIN_PRODUCT_TAG_VISIT_2": { + "type": ["null", "number"] + }, + "IMPRESSION_1": { + "type": ["null", "number"] + }, + "IMPRESSION_1_GROSS": { + "type": ["null", "number"] + }, + "IMPRESSION_2": { + "type": ["null", "number"] + }, + "INAPP_CHECKOUT_COST_PER_ACTION": { + "type": ["null", "number"] + }, + "OUTBOUND_CLICK_1": { + "type": ["null", "number"] + }, + "OUTBOUND_CLICK_2": { + "type": ["null", "number"] + }, + "PAGE_VISIT_COST_PER_ACTION": { + "type": ["null", "number"] + }, + "PAGE_VISIT_ROAS": { + "type": ["null", "number"] + }, + "PAID_IMPRESSION": { + "type": ["null", "number"] + }, + "PIN_ID": { + "type": ["null", "number"] + }, + "PIN_PROMOTION_ID": { + "type": ["null", "number"] + }, + "REPIN_1": { + "type": ["null", "number"] + }, + "REPIN_2": { + "type": ["null", "number"] + }, + "REPIN_RATE": { + "type": ["null", "number"] + }, + "SPEND_IN_DOLLAR": { + "type": ["null", "number"] + }, + "SPEND_IN_MICRO_DOLLAR": { + "type": ["null", "number"] + }, + "TOTAL_CHECKOUT": { + "type": ["null", "number"] + }, + "TOTAL_CHECKOUT_VALUE_IN_MICRO_DOLLAR": { + "type": ["null", "number"] + }, + "TOTAL_CLICKTHROUGH": { + "type": ["null", "number"] + }, + "TOTAL_CLICK_ADD_TO_CART": { + "type": ["null", "number"] + }, + "TOTAL_CLICK_CHECKOUT": { + "type": ["null", "number"] + }, + "TOTAL_CLICK_CHECKOUT_VALUE_IN_MICRO_DOLLAR": { + "type": ["null", "number"] + }, + "TOTAL_CLICK_LEAD": { + "type": ["null", "number"] + }, + "TOTAL_CLICK_SIGNUP": { + "type": ["null", "number"] + }, + "TOTAL_CLICK_SIGNUP_VALUE_IN_MICRO_DOLLAR": { + "type": ["null", "number"] + }, + "TOTAL_CONVERSIONS": { + "type": ["null", "number"] + }, + "TOTAL_CUSTOM": { + "type": ["null", "number"] + }, + "TOTAL_ENGAGEMENT": { + "type": ["null", "number"] + }, + "TOTAL_ENGAGEMENT_CHECKOUT": { + "type": ["null", "number"] + }, + "TOTAL_ENGAGEMENT_CHECKOUT_VALUE_IN_MICRO_DOLLAR": { + "type": ["null", "number"] + }, + "TOTAL_ENGAGEMENT_LEAD": { + "type": ["null", "number"] + }, + "TOTAL_ENGAGEMENT_SIGNUP": { + "type": ["null", "number"] + }, + "TOTAL_ENGAGEMENT_SIGNUP_VALUE_IN_MICRO_DOLLAR": { + "type": ["null", "number"] + }, + "TOTAL_IDEA_PIN_PRODUCT_TAG_VISIT": { + "type": ["null", "number"] + }, + "TOTAL_IMPRESSION_FREQUENCY": { + "type": ["null", "number"] + }, + "TOTAL_IMPRESSION_USER": { + "type": ["null", "number"] + }, + "TOTAL_LEAD": { + "type": ["null", "number"] + }, + "TOTAL_OFFLINE_CHECKOUT": { + "type": ["null", "number"] + }, + "TOTAL_PAGE_VISIT": { + "type": ["null", "number"] + }, + "TOTAL_REPIN_RATE": { + "type": ["null", "number"] + }, + "TOTAL_SIGNUP": { + "type": ["null", "number"] + }, + "TOTAL_SIGNUP_VALUE_IN_MICRO_DOLLAR": { + "type": ["null", "number"] + }, + "TOTAL_VIDEO_3SEC_VIEWS": { + "type": ["null", "number"] + }, + "TOTAL_VIDEO_AVG_WATCHTIME_IN_SECOND": { + "type": ["null", "number"] + }, + "TOTAL_VIDEO_MRC_VIEWS": { + "type": ["null", "number"] + }, + "TOTAL_VIDEO_P0_COMBINED": { + "type": ["null", "number"] + }, + "TOTAL_VIDEO_P100_COMPLETE": { + "type": ["null", "number"] + }, + "TOTAL_VIDEO_P25_COMBINED": { + "type": ["null", "number"] + }, + "TOTAL_VIDEO_P50_COMBINED": { + "type": ["null", "number"] + }, + "TOTAL_VIDEO_P75_COMBINED": { + "type": ["null", "number"] + }, + "TOTAL_VIDEO_P95_COMBINED": { + "type": ["null", "number"] + }, + "TOTAL_VIEW_ADD_TO_CART": { + "type": ["null", "number"] + }, + "TOTAL_VIEW_CHECKOUT": { + "type": ["null", "number"] + }, + "TOTAL_VIEW_CHECKOUT_VALUE_IN_MICRO_DOLLAR": { + "type": ["null", "number"] + }, + "TOTAL_VIEW_LEAD": { + "type": ["null", "number"] + }, + "TOTAL_VIEW_SIGNUP": { + "type": ["null", "number"] + }, + "TOTAL_VIEW_SIGNUP_VALUE_IN_MICRO_DOLLAR": { + "type": ["null", "number"] + }, + "TOTAL_WEB_CHECKOUT": { + "type": ["null", "number"] + }, + "TOTAL_WEB_CHECKOUT_VALUE_IN_MICRO_DOLLAR": { + "type": ["null", "number"] + }, + "TOTAL_WEB_CLICK_CHECKOUT": { + "type": ["null", "number"] + }, + "TOTAL_WEB_CLICK_CHECKOUT_VALUE_IN_MICRO_DOLLAR": { + "type": ["null", "number"] + }, + "TOTAL_WEB_ENGAGEMENT_CHECKOUT": { + "type": ["null", "number"] + }, + "TOTAL_WEB_ENGAGEMENT_CHECKOUT_VALUE_IN_MICRO_DOLLAR": { + "type": ["null", "number"] + }, + "TOTAL_WEB_SESSIONS": { + "type": ["null", "number"] + }, + "TOTAL_WEB_VIEW_CHECKOUT": { + "type": ["null", "number"] + }, + "TOTAL_WEB_VIEW_CHECKOUT_VALUE_IN_MICRO_DOLLAR": { + "type": ["null", "number"] + }, + "VIDEO_3SEC_VIEWS_2": { + "type": ["null", "number"] + }, + "VIDEO_LENGTH": { + "type": ["null", "number"] + }, + "VIDEO_MRC_VIEWS_2": { + "type": ["null", "number"] + }, + "VIDEO_P0_COMBINED_2": { + "type": ["null", "number"] + }, + "VIDEO_P100_COMPLETE_2": { + "type": ["null", "number"] + }, + "VIDEO_P25_COMBINED_2": { + "type": ["null", "number"] + }, + "VIDEO_P50_COMBINED_2": { + "type": ["null", "number"] + }, + "VIDEO_P75_COMBINED_2": { + "type": ["null", "number"] + }, + "VIDEO_P95_COMBINED_2": { + "type": ["null", "number"] + }, + "WEB_CHECKOUT_COST_PER_ACTION": { + "type": ["null", "number"] + }, + "WEB_CHECKOUT_ROAS": { + "type": ["null", "number"] + }, + "WEB_SESSIONS_1": { + "type": ["null", "number"] + }, + "WEB_SESSIONS_2": { + "type": ["null", "number"] + } + } +} diff --git a/airbyte-integrations/connectors/source-pinterest/source_pinterest/source.py b/airbyte-integrations/connectors/source-pinterest/source_pinterest/source.py index e110f15f339b..828e07f22b53 100644 --- a/airbyte-integrations/connectors/source-pinterest/source_pinterest/source.py +++ b/airbyte-integrations/connectors/source-pinterest/source_pinterest/source.py @@ -15,6 +15,19 @@ from airbyte_cdk.utils import AirbyteTracedException from source_pinterest.reports import CampaignAnalyticsReport +from .reports.reports import ( + AdGroupReport, + AdGroupTargetingReport, + AdvertizerReport, + AdvertizerTargetingReport, + CampaignTargetingReport, + KeywordReport, + PinPromotionReport, + PinPromotionTargetingReport, + ProductGroupReport, + ProductGroupTargetingReport, + ProductItemReport, +) from .streams import ( AdAccountAnalytics, AdAccounts, @@ -22,12 +35,19 @@ AdGroupAnalytics, AdGroups, Ads, + Audiences, BoardPins, Boards, BoardSectionPins, BoardSections, CampaignAnalytics, Campaigns, + Catalogs, + CatalogsFeeds, + CatalogsProductGroups, + ConversionTags, + CustomerLists, + Keywords, PinterestStream, UserAccountAnalytics, ) @@ -76,11 +96,16 @@ def check_connection(self, logger, config) -> Tuple[bool, any]: config = self._validate_and_transform(config) authenticator = self.get_authenticator(config) url = f"{PinterestStream.url_base}user_account" - auth_headers = {"Accept": "application/json", **authenticator.get_auth_header()} try: + auth_headers = {"Accept": "application/json", **authenticator.get_auth_header()} session = requests.get(url, headers=auth_headers) session.raise_for_status() return True, None + except requests.exceptions.HTTPError as e: + if "401 Client Error: Unauthorized for url" in str(e): + return False, f"Try to re-authenticate because current refresh token is not valid. {e}" + else: + return False, e except requests.exceptions.RequestException as e: return False, e @@ -89,19 +114,44 @@ def streams(self, config: Mapping[str, Any]) -> List[Stream]: report_config = self._validate_and_transform(config, amount_of_days_allowed_for_lookup=913) config = self._validate_and_transform(config) status = ",".join(config.get("status")) if config.get("status") else None + + ad_accounts = AdAccounts(config) + ads = Ads(ad_accounts, config=config, status_filter=status) + ad_groups = AdGroups(ad_accounts, config=config, status_filter=status) + campaigns = Campaigns(ad_accounts, config=config, status_filter=status) + boards = Boards(config) + board_sections = BoardSections(boards, config=config) return [ - AdAccountAnalytics(AdAccounts(config), config=config), - AdAccounts(config), - AdAnalytics(Ads(AdAccounts(config), with_data_slices=False, config=config), config=config), - AdGroupAnalytics(AdGroups(AdAccounts(config), with_data_slices=False, config=config), config=config), - AdGroups(AdAccounts(config), status_filter=status, config=config), - Ads(AdAccounts(config), status_filter=status, config=config), - BoardPins(Boards(config), config=config), - BoardSectionPins(BoardSections(Boards(config), config=config), config=config), - BoardSections(Boards(config), config=config), - Boards(config), - CampaignAnalytics(Campaigns(AdAccounts(config), with_data_slices=False, config=config), config=config), - CampaignAnalyticsReport(AdAccounts(report_config), config=report_config), - Campaigns(AdAccounts(config), status_filter=status, config=config), + ad_accounts, + AdAccountAnalytics(ad_accounts, config=config), + ads, + AdAnalytics(ads, config=config), + ad_groups, + AdGroupAnalytics(ad_groups, config=config), + boards, + BoardPins(boards, config=config), + board_sections, + BoardSectionPins(board_sections, config=config), + campaigns, + CampaignAnalytics(campaigns, config=config), + CampaignAnalyticsReport(ad_accounts, config=report_config), + CampaignTargetingReport(ad_accounts, config=report_config), UserAccountAnalytics(None, config=config), + Keywords(ad_groups, config=config), + Audiences(ad_accounts, config=config), + ConversionTags(ad_accounts, config=config), + CustomerLists(ad_accounts, config=config), + Catalogs(config=config), + CatalogsFeeds(config=config), + CatalogsProductGroups(config=config), + AdvertizerReport(ad_accounts, config=report_config), + AdvertizerTargetingReport(ad_accounts, config=report_config), + AdGroupReport(ad_accounts, config=report_config), + AdGroupTargetingReport(ad_accounts, config=report_config), + PinPromotionReport(ad_accounts, config=report_config), + PinPromotionTargetingReport(ad_accounts, config=report_config), + ProductGroupReport(ad_accounts, config=report_config), + ProductGroupTargetingReport(ad_accounts, config=report_config), + KeywordReport(ad_accounts, config=report_config), + ProductItemReport(ad_accounts, config=report_config), ] diff --git a/airbyte-integrations/connectors/source-pinterest/source_pinterest/streams.py b/airbyte-integrations/connectors/source-pinterest/source_pinterest/streams.py index 906e707b976d..0f30a44e093a 100644 --- a/airbyte-integrations/connectors/source-pinterest/source_pinterest/streams.py +++ b/airbyte-integrations/connectors/source-pinterest/source_pinterest/streams.py @@ -2,16 +2,21 @@ # Copyright (c) 2023 Airbyte, Inc., all rights reserved. # +import logging from abc import ABC from datetime import datetime -from typing import Any, Iterable, List, Mapping, MutableMapping, Optional +from typing import Any, Dict, Iterable, List, Mapping, MutableMapping, Optional import pendulum import requests from airbyte_cdk.models import SyncMode +from airbyte_cdk.sources import Source +from airbyte_cdk.sources.streams import Stream from airbyte_cdk.sources.streams.availability_strategy import AvailabilityStrategy from airbyte_cdk.sources.streams.http import HttpStream, HttpSubStream +from airbyte_cdk.sources.streams.http.availability_strategy import HttpAvailabilityStrategy from airbyte_cdk.sources.utils.transform import TransformConfig, TypeTransformer +from requests import HTTPError from .utils import get_analytics_columns, to_datetime_str @@ -44,10 +49,6 @@ def start_date(self): def window_in_days(self): return 30 # Set window_in_days to 30 days date range - @property - def availability_strategy(self) -> Optional["AvailabilityStrategy"]: - return None - def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]: next_page = response.json().get("bookmark", {}) if self.data_fields else {} @@ -115,6 +116,53 @@ def path(self, **kwargs) -> str: return "boards" +class Catalogs(PinterestStream): + """Docs: https://developers.pinterest.com/docs/api/v5/#operation/catalogs/list""" + + use_cache = True + + def path(self, **kwargs) -> str: + return "catalogs" + + +class CatalogsFeeds(PinterestStream): + """Docs: https://developers.pinterest.com/docs/api/v5/#operation/feeds/list""" + + use_cache = True + + def path(self, **kwargs) -> str: + return "catalogs/feeds" + + def parse_response(self, response: requests.Response, stream_state: Mapping[str, Any], **kwargs) -> Iterable[Mapping]: + # Remove sensitive data + for record in super().parse_response(response, stream_state, **kwargs): + record.pop("credentials", None) + yield record + + +class CatalogsProductGroupsAvailabilityStrategy(HttpAvailabilityStrategy): + def reasons_for_unavailable_status_codes( + self, stream: Stream, logger: logging.Logger, source: Optional[Source], error: HTTPError + ) -> Dict[int, str]: + reasons_for_codes: Dict[int, str] = super().reasons_for_unavailable_status_codes(stream, logger, source, error) + reasons_for_codes[409] = "Can't access catalog product groups because there is no existing catalog." + + return reasons_for_codes + + +class CatalogsProductGroups(PinterestStream): + """Docs: https://developers.pinterest.com/docs/api/v5/#operation/catalogs_product_groups/list""" + + use_cache = True + + def path(self, **kwargs) -> str: + return "catalogs/product_groups" + + @property + def availability_strategy(self) -> Optional["AvailabilityStrategy"]: + return CatalogsProductGroupsAvailabilityStrategy() + + class AdAccounts(PinterestStream): use_cache = True @@ -137,6 +185,34 @@ def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: return f"boards/{stream_slice['sub_parent']['parent']['id']}/sections/{stream_slice['parent']['id']}/pins" +class Audiences(PinterestSubStream, PinterestStream): + """Docs: https://developers.pinterest.com/docs/api/v5/#operation/audiences/list""" + + def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: + return f"ad_accounts/{stream_slice['parent']['id']}/audiences" + + +class Keywords(PinterestSubStream, PinterestStream): + """Docs: https://developers.pinterest.com/docs/api/v5/#operation/keywords/get""" + + def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: + return f"ad_accounts/{stream_slice['parent']['ad_account_id']}/keywords?ad_group_id={stream_slice['parent']['id']}" + + +class ConversionTags(PinterestSubStream, PinterestStream): + """Docs: https://developers.pinterest.com/docs/api/v5/#operation/conversion_tags/list""" + + def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: + return f"ad_accounts/{stream_slice['parent']['id']}/conversion_tags" + + +class CustomerLists(PinterestSubStream, PinterestStream): + """Docs: https://developers.pinterest.com/docs/api/v5/#tag/customer_lists""" + + def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: + return f"ad_accounts/{stream_slice['parent']['id']}/customer_lists" + + class IncrementalPinterestStream(PinterestStream, ABC): def get_updated_state(self, current_stream_state: MutableMapping[str, Any], latest_record: Mapping[str, Any]) -> Mapping[str, Any]: default_value = self.start_date.format("YYYY-MM-DD") @@ -292,7 +368,7 @@ def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: class Campaigns(ServerSideFilterStream): - def __init__(self, parent: HttpStream, with_data_slices: bool = True, status_filter: str = "", **kwargs): + def __init__(self, parent: HttpStream, with_data_slices: bool = False, status_filter: str = "", **kwargs): super().__init__(parent, with_data_slices, **kwargs) self.status_filter = status_filter @@ -309,11 +385,12 @@ def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: class AdGroups(ServerSideFilterStream): - def __init__(self, parent: HttpStream, with_data_slices: bool = True, status_filter: str = "", **kwargs): + def __init__(self, parent: HttpStream, with_data_slices: bool = False, status_filter: str = "", **kwargs): super().__init__(parent, with_data_slices, **kwargs) self.status_filter = status_filter def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: + print(f"=========== stream_slice: {stream_slice} =====================") params = f"?entity_statuses={self.status_filter}" if self.status_filter else "" return f"ad_accounts/{stream_slice['parent']['id']}/ad_groups{params}" @@ -326,7 +403,7 @@ def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: class Ads(ServerSideFilterStream): - def __init__(self, parent: HttpStream, with_data_slices: bool = True, status_filter: str = "", **kwargs): + def __init__(self, parent: HttpStream, with_data_slices: bool = False, status_filter: str = "", **kwargs): super().__init__(parent, with_data_slices, **kwargs) self.status_filter = status_filter diff --git a/airbyte-integrations/connectors/source-pinterest/unit_tests/test_reports.py b/airbyte-integrations/connectors/source-pinterest/unit_tests/test_reports.py index 5638ceebe77e..8cc1a4f96057 100644 --- a/airbyte-integrations/connectors/source-pinterest/unit_tests/test_reports.py +++ b/airbyte-integrations/connectors/source-pinterest/unit_tests/test_reports.py @@ -3,7 +3,9 @@ # import responses +from source_pinterest import SourcePinterest from source_pinterest.utils import get_analytics_columns +from unit_tests.test_source import setup_responses @responses.activate @@ -51,3 +53,12 @@ def test_read_records(analytics_report_stream, date_range): assert next(records) == expected_record assert len(responses.calls) == 3 assert responses.calls[0].request.url == report_request_url + + +@responses.activate +def test_streams(test_config): + setup_responses() + source = SourcePinterest() + streams = source.streams(test_config) + expected_streams_number = 32 + assert len(streams) == expected_streams_number diff --git a/airbyte-integrations/connectors/source-pinterest/unit_tests/test_source.py b/airbyte-integrations/connectors/source-pinterest/unit_tests/test_source.py index 8c3a112c0711..d86620fad40c 100644 --- a/airbyte-integrations/connectors/source-pinterest/unit_tests/test_source.py +++ b/airbyte-integrations/connectors/source-pinterest/unit_tests/test_source.py @@ -40,12 +40,17 @@ def test_check_wrong_date_connection(wrong_date_config): @responses.activate -def test_streams(test_config): - setup_responses() +def test_check_connection_expired_token(test_config): + responses.add( + responses.POST, + "https://api.pinterest.com/v5/oauth/token", + status=401 + ) source = SourcePinterest() - streams = source.streams(test_config) - expected_streams_number = 14 - assert len(streams) == expected_streams_number + logger_mock = MagicMock() + assert source.check_connection(logger_mock, test_config) == (False, + 'Try to re-authenticate because current refresh token is not valid. ' \ + '401 Client Error: Unauthorized for url: https://api.pinterest.com/v5/oauth/token') def test_get_authenticator(test_config): diff --git a/airbyte-integrations/connectors/source-pinterest/unit_tests/test_streams.py b/airbyte-integrations/connectors/source-pinterest/unit_tests/test_streams.py index a13930e3daab..a560986ec482 100644 --- a/airbyte-integrations/connectors/source-pinterest/unit_tests/test_streams.py +++ b/airbyte-integrations/connectors/source-pinterest/unit_tests/test_streams.py @@ -15,12 +15,19 @@ AdGroupAnalytics, AdGroups, Ads, + Audiences, BoardPins, Boards, BoardSectionPins, BoardSections, CampaignAnalytics, Campaigns, + Catalogs, + CatalogsFeeds, + CatalogsProductGroups, + ConversionTags, + CustomerLists, + Keywords, PinterestStream, PinterestSubStream, UserAccountAnalytics, @@ -64,6 +71,15 @@ def test_parse_response(patch_base_class, test_response, test_current_stream_sta assert next(stream.parse_response(**inputs)) == expected_parsed_object +def test_parse_response_with_sensitive_data(patch_base_class): + """Test that sensitive data is removed""" + stream = CatalogsFeeds(config=MagicMock()) + response = MagicMock() + response.json.return_value = {"items": [{"id": "CatalogsFeeds1", "credentials": {"password": "bla"}}], "bookmark": "string"} + actual_response = list(stream.parse_response(response=response, stream_state=None)) + assert actual_response == [{"id": "CatalogsFeeds1"}] + + def test_request_headers(patch_base_class): stream = PinterestStream(config=MagicMock()) inputs = {"stream_slice": None, "stream_state": None, "next_page_token": None} @@ -180,6 +196,17 @@ def test_backoff_on_rate_limit_error(requests_mock, test_response, status_code, {"sub_parent": {"parent": {"id": "234"}}, "parent": {"id": "123"}}, "ad_accounts/234/ads/analytics", ), + (Catalogs(config=MagicMock()), None, "catalogs"), + (CatalogsFeeds(config=MagicMock()), None, "catalogs/feeds"), + (CatalogsProductGroups(config=MagicMock()), None, "catalogs/product_groups"), + ( + Keywords(parent=None, config=MagicMock()), + {"parent": {"id": "234", "ad_account_id": "AD_ACCOUNT_1"}}, + "ad_accounts/AD_ACCOUNT_1/keywords?ad_group_id=234", + ), + (Audiences(parent=None, config=MagicMock()), {"parent": {"id": "AD_ACCOUNT_1"}}, "ad_accounts/AD_ACCOUNT_1/audiences"), + (ConversionTags(parent=None, config=MagicMock()), {"parent": {"id": "AD_ACCOUNT_1"}}, "ad_accounts/AD_ACCOUNT_1/conversion_tags"), + (CustomerLists(parent=None, config=MagicMock()), {"parent": {"id": "AD_ACCOUNT_1"}}, "ad_accounts/AD_ACCOUNT_1/customer_lists"), ], ) def test_path(patch_base_class, stream_cls, slice, expected): diff --git a/docs/integrations/sources/pinterest.md b/docs/integrations/sources/pinterest.md index fa54c2ed62b4..aa599ef5389e 100644 --- a/docs/integrations/sources/pinterest.md +++ b/docs/integrations/sources/pinterest.md @@ -46,57 +46,70 @@ The Pinterest source connector supports the following [sync modes](https://docs. ## Supported Streams - [Account analytics](https://developers.pinterest.com/docs/api/v5/#operation/user_account/analytics) \(Incremental\) -- [Boards](https://developers.pinterest.com/docs/api/v5/#operation/boards/list) \(Full table\) - - [Board sections](https://developers.pinterest.com/docs/api/v5/#operation/board_sections/list) \(Full table\) - - [Pins on board section](https://developers.pinterest.com/docs/api/v5/#operation/board_sections/list_pins) \(Full table\) - - [Pins on board](https://developers.pinterest.com/docs/api/v5/#operation/boards/list_pins) \(Full table\) -- [Ad accounts](https://developers.pinterest.com/docs/api/v5/#operation/ad_accounts/list) \(Full table\) - - [Ad account analytics](https://developers.pinterest.com/docs/api/v5/#operation/ad_account/analytics) \(Incremental\) - - [Campaigns](https://developers.pinterest.com/docs/api/v5/#operation/campaigns/list) \(Incremental\) - - [Campaign analytics](https://developers.pinterest.com/docs/api/v5/#operation/campaigns/list) \(Incremental\) - - [Campaign Analytics Report](https://developers.pinterest.com/docs/api/v5/#operation/analytics/create_report) \(Incremental\) - - [Ad groups](https://developers.pinterest.com/docs/api/v5/#operation/ad_groups/list) \(Incremental\) - - [Ad group analytics](https://developers.pinterest.com/docs/api/v5/#operation/ad_groups/analytics) \(Incremental\) - - [Ads](https://developers.pinterest.com/docs/api/v5/#operation/ads/list) \(Incremental\) - - [Ad analytics](https://developers.pinterest.com/docs/api/v5/#operation/ads/analytics) \(Incremental\) +- [Boards](https://developers.pinterest.com/docs/api/v5/#operation/boards/list) \(Full refresh\) +- [Board sections](https://developers.pinterest.com/docs/api/v5/#operation/board_sections/list) \(Full refresh\) +- [Pins on board section](https://developers.pinterest.com/docs/api/v5/#operation/board_sections/list_pins) \(Full refresh\) +- [Pins on board](https://developers.pinterest.com/docs/api/v5/#operation/boards/list_pins) \(Full refresh\) +- [Ad accounts](https://developers.pinterest.com/docs/api/v5/#operation/ad_accounts/list) \(Full refresh\) +- [Ad account analytics](https://developers.pinterest.com/docs/api/v5/#operation/ad_account/analytics) \(Incremental\) +- [Campaigns](https://developers.pinterest.com/docs/api/v5/#operation/campaigns/list) \(Incremental\) +- [Campaign analytics](https://developers.pinterest.com/docs/api/v5/#operation/campaigns/list) \(Incremental\) +- [Campaign Analytics Report](https://developers.pinterest.com/docs/api/v5/#operation/analytics/create_report) \(Incremental\) +- [Campaign Targeting Report](https://developers.pinterest.com/docs/api/v5/#operation/analytics/create_report) \(Incremental\) +- [Ad Groups](https://developers.pinterest.com/docs/api/v5/#operation/ad_groups/list) \(Incremental\) +- [Ad Group Analytics](https://developers.pinterest.com/docs/api/v5/#operation/ad_groups/analytics) \(Incremental\) +- [Ad Group Report](https://developers.pinterest.com/docs/api/v5/#operation/ad_groups/analytics) \(Incremental\) +- [Ad Group Targeting Report](https://developers.pinterest.com/docs/api/v5/#operation/ad_groups/analytics) \(Incremental\) +- [Ads](https://developers.pinterest.com/docs/api/v5/#operation/ads/list) \(Incremental\) +- [Ad analytics](https://developers.pinterest.com/docs/api/v5/#operation/ads/analytics) \(Incremental\) +- [Catalogs](https://developers.pinterest.com/docs/api/v5/#operation/catalogs/list) \(Full refresh\) +- [Catalogs Feeds](https://developers.pinterest.com/docs/api/v5/#operation/feeds/list) \(Full refresh\) +- [Catalogs Product Groups](https://developers.pinterest.com/docs/api/v5/#operation/catalogs_product_groups/list) \(Full refresh\) +- [Audiences](https://developers.pinterest.com/docs/api/v5/#operation/audiences/list) \(Full refresh\) +- [Keywords](https://developers.pinterest.com/docs/api/v5/#operation/keywords/get) \(Full refresh\) +- [Conversion Tags](https://developers.pinterest.com/docs/api/v5/#operation/conversion_tags/list) \(Full refresh\) +- [Customer Lists](https://developers.pinterest.com/docs/api/v5/#tag/customer_lists) \(Full refresh\) +- [Advertizer Report](https://developers.pinterest.com/docs/api/v5/#operation/analytics/create_report) \(Incremental\) +- [Advertizer Targeting Report](https://developers.pinterest.com/docs/api/v5/#operation/analytics/create_report) \(Incremental\) +- [Pin Promotion Report](https://developers.pinterest.com/docs/api/v5/#operation/analytics/create_report) \(Incremental\) +- [Pin Promotion Targeting Report](https://developers.pinterest.com/docs/api/v5/#operation/analytics/create_report) \(Incremental\) +- [Product Group Report](https://developers.pinterest.com/docs/api/v5/#operation/analytics/create_report) \(Incremental\) +- [Product Group Targeting Report](https://developers.pinterest.com/docs/api/v5/#operation/analytics/create_report) \(Incremental\) +- [Product Item Report](https://developers.pinterest.com/docs/api/v5/#operation/analytics/create_report) \(Incremental\) +- [Keyword Report](https://developers.pinterest.com/docs/api/v5/#operation/analytics/create_report) \(Incremental\) ## Performance considerations -The connector is restricted by the Pinterest [requests limitation](https://developers.pinterest.com/docs/api/v5/#tag/Rate-limits). - -##### Rate Limits - -- Analytics streams: 300 calls per day / per user \ -- Ad accounts streams (Campaigns, Ad groups, Ads): 1000 calls per min / per user / per app \ -- Boards streams: 10 calls per sec / per user / per app +The connector is restricted by the Pinterest [requests limitation](https://developers.pinterest.com/docs/reference/ratelimits/). ## Changelog -| Version | Date | Pull Request | Subject | -|:--------|:-----------| :------------------------------------------------------- |:--------------------------------------------------------------------------------------------------------------| -| 0.7.1 | 2023-11-01 | [32078](https://github.com/airbytehq/airbyte/pull/32078) | handle non json response | -| 0.7.0 | 2023-10-25 | [31876](https://github.com/airbytehq/airbyte/pull/31876) | Migrated to base image, removed token based authentication mthod becuase access_token is valid for 1 day only | -| 0.6.0 | 2023-07-25 | [28672](https://github.com/airbytehq/airbyte/pull/28672) | Add report stream for `CAMPAIGN` level | -| 0.5.3 | 2023-07-05 | [27964](https://github.com/airbytehq/airbyte/pull/27964) | Add `id` field to `owner` field in `ad_accounts` stream | -| 0.5.2 | 2023-06-02 | [26949](https://github.com/airbytehq/airbyte/pull/26949) | Update `BoardPins` stream with `note` property | -| 0.5.1 | 2023-05-11 | [25984](https://github.com/airbytehq/airbyte/pull/25984) | Add pattern for start_date | -| 0.5.0 | 2023-05-17 | [26188](https://github.com/airbytehq/airbyte/pull/26188) | Add `product_tags` field to the `BoardPins` stream | -| 0.4.0 | 2023-05-16 | [26112](https://github.com/airbytehq/airbyte/pull/26112) | Add `is_standard` field to the `BoardPins` stream | -| 0.3.0 | 2023-05-09 | [25915](https://github.com/airbytehq/airbyte/pull/25915) | Add `creative_type` field to the `BoardPins` stream | -| 0.2.6 | 2023-04-26 | [25548](https://github.com/airbytehq/airbyte/pull/25548) | Fix `format` issue for `boards` stream schema for fields with `date-time` | -| 0.2.5 | 2023-04-19 | [00000](https://github.com/airbytehq/airbyte/pull/00000) | Update `AMOUNT_OF_DAYS_ALLOWED_FOR_LOOKUP` to 89 days | -| 0.2.4 | 2023-02-25 | [23457](https://github.com/airbytehq/airbyte/pull/23457) | Add missing columns for analytics streams for pinterest source | -| 0.2.3 | 2023-03-01 | [23649](https://github.com/airbytehq/airbyte/pull/23649) | Fix for `HTTP - 400 Bad Request` when requesting data >= 90 days | -| 0.2.2 | 2023-01-27 | [22020](https://github.com/airbytehq/airbyte/pull/22020) | Set `AvailabilityStrategy` for streams explicitly to `None` | -| 0.2.1 | 2022-12-15 | [20532](https://github.com/airbytehq/airbyte/pull/20532) | Bump CDK version | -| 0.2.0 | 2022-12-13 | [20242](https://github.com/airbytehq/airbyte/pull/20242) | Add data-type normalization up to the schemas declared | -| 0.1.9 | 2022-09-06 | [15074](https://github.com/airbytehq/airbyte/pull/15074) | Add filter based on statuses | -| 0.1.8 | 2022-10-21 | [18285](https://github.com/airbytehq/airbyte/pull/18285) | Fix type of `start_date` | -| 0.1.7 | 2022-09-29 | [17387](https://github.com/airbytehq/airbyte/pull/17387) | Set `start_date` dynamically based on API restrictions. | -| 0.1.6 | 2022-09-28 | [17304](https://github.com/airbytehq/airbyte/pull/17304) | Use CDK 0.1.89 | -| 0.1.5 | 2022-09-16 | [16799](https://github.com/airbytehq/airbyte/pull/16799) | Migrate to per-stream state | -| 0.1.4 | 2022-09-06 | [16161](https://github.com/airbytehq/airbyte/pull/16161) | Add ability to handle `429 - Too Many Requests` error with respect to `Max Rate Limit Exceeded Error` | -| 0.1.3 | 2022-09-02 | [16271](https://github.com/airbytehq/airbyte/pull/16271) | Add support of `OAuth2.0` authentication method | -| 0.1.2 | 2021-12-22 | [10223](https://github.com/airbytehq/airbyte/pull/10223) | Fix naming of `AD_ID` and `AD_ACCOUNT_ID` fields | -| 0.1.1 | 2021-12-22 | [9043](https://github.com/airbytehq/airbyte/pull/9043) | Update connector fields title/description | -| 0.1.0 | 2021-10-29 | [7493](https://github.com/airbytehq/airbyte/pull/7493) | Release Pinterest CDK Connector | +| Version | Date | Pull Request | Subject | +|:--------|:-----------| :------------------------------------------------------- |:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 0.7.2 | 2023-11-08 | [32299](https://github.com/airbytehq/airbyte/pull/32299) | added default `AvailabilityStrategy`, fixed bug which cases duplicated requests, added new streams: Catalogs, CatalogsFeeds, CatalogsProductGroups, Audiences, Keywords, ConversionTags, CustomerLists, CampaignTargetingReport, AdvertizerReport, AdvertizerTargetingReport, AdGroupReport, AdGroupTargetingReport, PinPromotionReport, PinPromotionTargetingReport, ProductGroupReport, ProductGroupTargetingReport, ProductItemReport, KeywordReport | +| 0.7.1 | 2023-11-01 | [32078](https://github.com/airbytehq/airbyte/pull/32078) | handle non json response | +| 0.7.0 | 2023-10-25 | [31876](https://github.com/airbytehq/airbyte/pull/31876) | Migrated to base image, removed token based authentication mthod becuase access_token is valid for 1 day only | +| 0.6.0 | 2023-07-25 | [28672](https://github.com/airbytehq/airbyte/pull/28672) | Add report stream for `CAMPAIGN` level | +| 0.5.3 | 2023-07-05 | [27964](https://github.com/airbytehq/airbyte/pull/27964) | Add `id` field to `owner` field in `ad_accounts` stream | +| 0.5.2 | 2023-06-02 | [26949](https://github.com/airbytehq/airbyte/pull/26949) | Update `BoardPins` stream with `note` property | +| 0.5.1 | 2023-05-11 | [25984](https://github.com/airbytehq/airbyte/pull/25984) | Add pattern for start_date | +| 0.5.0 | 2023-05-17 | [26188](https://github.com/airbytehq/airbyte/pull/26188) | Add `product_tags` field to the `BoardPins` stream | +| 0.4.0 | 2023-05-16 | [26112](https://github.com/airbytehq/airbyte/pull/26112) | Add `is_standard` field to the `BoardPins` stream | +| 0.3.0 | 2023-05-09 | [25915](https://github.com/airbytehq/airbyte/pull/25915) | Add `creative_type` field to the `BoardPins` stream | +| 0.2.6 | 2023-04-26 | [25548](https://github.com/airbytehq/airbyte/pull/25548) | Fix `format` issue for `boards` stream schema for fields with `date-time` | +| 0.2.5 | 2023-04-19 | [00000](https://github.com/airbytehq/airbyte/pull/00000) | Update `AMOUNT_OF_DAYS_ALLOWED_FOR_LOOKUP` to 89 days | +| 0.2.4 | 2023-02-25 | [23457](https://github.com/airbytehq/airbyte/pull/23457) | Add missing columns for analytics streams for pinterest source | +| 0.2.3 | 2023-03-01 | [23649](https://github.com/airbytehq/airbyte/pull/23649) | Fix for `HTTP - 400 Bad Request` when requesting data >= 90 days | +| 0.2.2 | 2023-01-27 | [22020](https://github.com/airbytehq/airbyte/pull/22020) | Set `AvailabilityStrategy` for streams explicitly to `None` | +| 0.2.1 | 2022-12-15 | [20532](https://github.com/airbytehq/airbyte/pull/20532) | Bump CDK version | +| 0.2.0 | 2022-12-13 | [20242](https://github.com/airbytehq/airbyte/pull/20242) | Add data-type normalization up to the schemas declared | +| 0.1.9 | 2022-09-06 | [15074](https://github.com/airbytehq/airbyte/pull/15074) | Add filter based on statuses | +| 0.1.8 | 2022-10-21 | [18285](https://github.com/airbytehq/airbyte/pull/18285) | Fix type of `start_date` | +| 0.1.7 | 2022-09-29 | [17387](https://github.com/airbytehq/airbyte/pull/17387) | Set `start_date` dynamically based on API restrictions. | +| 0.1.6 | 2022-09-28 | [17304](https://github.com/airbytehq/airbyte/pull/17304) | Use CDK 0.1.89 | +| 0.1.5 | 2022-09-16 | [16799](https://github.com/airbytehq/airbyte/pull/16799) | Migrate to per-stream state | +| 0.1.4 | 2022-09-06 | [16161](https://github.com/airbytehq/airbyte/pull/16161) | Add ability to handle `429 - Too Many Requests` error with respect to `Max Rate Limit Exceeded Error` | +| 0.1.3 | 2022-09-02 | [16271](https://github.com/airbytehq/airbyte/pull/16271) | Add support of `OAuth2.0` authentication method | +| 0.1.2 | 2021-12-22 | [10223](https://github.com/airbytehq/airbyte/pull/10223) | Fix naming of `AD_ID` and `AD_ACCOUNT_ID` fields | +| 0.1.1 | 2021-12-22 | [9043](https://github.com/airbytehq/airbyte/pull/9043) | Update connector fields title/description | +| 0.1.0 | 2021-10-29 | [7493](https://github.com/airbytehq/airbyte/pull/7493) | Release Pinterest CDK Connector |