Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🎉 Source Amazon Ads : Add attribution reports #16342

Merged
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8ac8cee
use data field for json response
ganpatagarwal Sep 6, 2022
e8998ed
add attribution reports
ganpatagarwal Sep 6, 2022
4d86c53
update changelog
ganpatagarwal Sep 6, 2022
57c3808
Merge branch 'master' of https://github.com/airbytehq/airbyte into am…
ganpatagarwal Sep 7, 2022
d12a9bd
Merge branch 'master' of https://github.com/airbytehq/airbyte into am…
ganpatagarwal Sep 9, 2022
e213fc4
add atrribution report integration test
ganpatagarwal Sep 9, 2022
3634a83
Merge branch 'master' of https://github.com/airbytehq/airbyte into am…
ganpatagarwal Sep 12, 2022
08dfd90
Merge branch 'master' of https://github.com/airbytehq/airbyte into am…
ganpatagarwal Sep 12, 2022
4a6f9e5
Merge branch 'master' of https://github.com/airbytehq/airbyte into am…
ganpatagarwal Sep 14, 2022
1b4b833
clean up expected_records
YiyangLi Sep 14, 2022
91d0682
Merge branch 'master' of https://github.com/airbytehq/airbyte into am…
ganpatagarwal Sep 15, 2022
8764d9f
Merge branch 'master' of https://github.com/airbytehq/airbyte into am…
ganpatagarwal Sep 17, 2022
108b81c
Merge branch 'master' into amazon-ads-attribution-reports
harshithmullapudi Sep 21, 2022
02c5b61
Merge branch 'master' of https://github.com/airbytehq/airbyte into am…
ganpatagarwal Sep 22, 2022
1b817b8
handle exception for profile
ganpatagarwal Sep 22, 2022
7744ca4
update tests
ganpatagarwal Sep 22, 2022
26e1694
Merge branch 'amazon-ads-attribution-reports' of https://github.com/g…
ganpatagarwal Sep 22, 2022
d65e7dc
Merge branch 'master' of https://github.com/airbytehq/airbyte into am…
ganpatagarwal Sep 26, 2022
e96b497
Merge branch 'master' of https://github.com/airbytehq/airbyte into am…
ganpatagarwal Sep 28, 2022
75baea2
Merge branch 'master' of https://github.com/airbytehq/airbyte into am…
ganpatagarwal Sep 29, 2022
7d7cd06
Merge branch 'master' of https://github.com/airbytehq/airbyte into am…
ganpatagarwal Sep 29, 2022
2abdeab
Merge branch 'master' of https://github.com/airbytehq/airbyte into am…
ganpatagarwal Sep 30, 2022
f0a78f4
Merge branch 'master' into amazon-ads-attribution-reports
sajarin Oct 3, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,19 @@ tests:
basic_read:
- config_path: "secrets/config.json"
configured_catalog_path: "integration_tests/configured_catalog.json"
empty_streams: ["sponsored_brands_campaigns", "sponsored_brands_ad_groups", "sponsored_brands_keywords"]
expect_records:
path: "integration_tests/expected_records.txt"
extra_fields: no
exact_order: no
extra_records: no
empty_streams:
[
"sponsored_brands_ad_groups",
"sponsored_brands_campaigns",
"sponsored_brands_keywords",
"sponsored_product_keywords",
"sponsored_product_negative_keywords",
"sponsored_product_targetings",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this streams: sponsored_product_keywords, sponsored_product_negative_keywords, sponsored_product_targetings

added as empty ?

Copy link
Contributor

@YiyangLi YiyangLi Sep 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it comes from me, irrelevant to the topic branch. It was about 2 weeks ago, the integration test says these streams are empty in the test account when I run it locally. And I have the login credential, but I can't access the OTP token, so I can't login.

"attribution_report_performance_creative",
"attribution_report_performance_adgroup",
"attribution_report_products",
"attribution_report_performance_campaign",
]
- config_path: "secrets/config_report.json"
configured_catalog_path: "integration_tests/configured_catalog_report.json"
timeout_seconds: 2400
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,42 @@
},
"sync_mode": "full_refresh",
"destination_sync_mode": "overwrite"
},
{
"stream": {
"name": "attribution_report_products",
"json_schema": {},
"supported_sync_modes": ["full_refresh"]
},
"sync_mode": "full_refresh",
"destination_sync_mode": "overwrite"
},
{
"stream": {
"name": "attribution_report_performance_adgroup",
"json_schema": {},
"supported_sync_modes": ["full_refresh"]
},
"sync_mode": "full_refresh",
"destination_sync_mode": "overwrite"
},
{
"stream": {
"name": "attribution_report_performance_campaign",
"json_schema": {},
"supported_sync_modes": ["full_refresh"]
},
"sync_mode": "full_refresh",
"destination_sync_mode": "overwrite"
},
{
"stream": {
"name": "attribution_report_performance_creative",
"json_schema": {},
"supported_sync_modes": ["full_refresh"]
},
"sync_mode": "full_refresh",
"destination_sync_mode": "overwrite"
}
]
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
from .attribution_report import AttributionReportModel
from .common import CatalogModel, Keywords, MetricsReport, NegativeKeywords
from .profile import Profile
from .sponsored_brands import BrandsAdGroup, BrandsCampaign
Expand All @@ -43,4 +44,5 @@
"ProductCampaign",
"ProductTargeting",
"Profile",
"AttributionReportModel",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#
# Copyright (c) 2022 Airbyte, Inc., all rights reserved.
#

from typing import List

from .common import CatalogModel


class Report(CatalogModel):
date: str
brandName: str
marketplace: str
campaignId: str
productAsin: str
productConversionType: str
advertiserName: str
adGroupId: str
creativeId: str
productName: str
productCategory: str
productSubcategory: str
productGroup: str
publisher: str


class AttributionReportModel(CatalogModel):
reports: List[Report]
size: int
cursorId: str
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@

from .schemas import Profile
from .streams import (
AttributionReportPerformanceAdgroup,
AttributionReportPerformanceCampaign,
AttributionReportPerformanceCreative,
AttributionReportProducts,
Profiles,
SponsoredBrandsAdGroups,
SponsoredBrandsCampaigns,
Expand Down Expand Up @@ -102,6 +106,10 @@ def streams(self, config: Mapping[str, Any]) -> List[Stream]:
SponsoredBrandsKeywords,
SponsoredBrandsReportStream,
SponsoredBrandsVideoReportStream,
AttributionReportPerformanceAdgroup,
AttributionReportPerformanceCampaign,
AttributionReportPerformanceCreative,
AttributionReportProducts,
]
return [profiles_stream, *[stream_class(**stream_args) for stream_class in non_profile_stream_classes]]

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
#
# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
#
from .attribution_report import (
AttributionReportPerformanceAdgroup,
AttributionReportPerformanceCampaign,
AttributionReportPerformanceCreative,
AttributionReportProducts,
)
from .profiles import Profiles
from .report_streams import (
SponsoredBrandsReportStream,
Expand Down Expand Up @@ -38,4 +44,8 @@
"SponsoredProductsReportStream",
"SponsoredBrandsReportStream",
"SponsoredBrandsVideoReportStream",
"AttributionReportPerformanceAdgroup",
"AttributionReportPerformanceCampaign",
"AttributionReportPerformanceCreative",
"AttributionReportProducts",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#
# Copyright (c) 2022 Airbyte, Inc., all rights reserved.
#

from typing import Any, Iterable, Mapping, MutableMapping, Optional

import pendulum
import requests
from source_amazon_ads.schemas import AttributionReportModel, Profile
from source_amazon_ads.streams.common import AmazonAdsStream

BRAND_REFERRAL_BONUS = "brb_bonus_amount"

METRICS_MAP = {
"PERFORMANCE": [
"Click-throughs",
"attributedDetailPageViewsClicks14d",
"attributedAddToCartClicks14d",
"attributedPurchases14d",
"unitsSold14d",
"attributedSales14d",
"attributedTotalDetailPageViewsClicks14d",
"attributedTotalAddToCartClicks14d",
"attributedTotalPurchases14d",
"totalUnitsSold14d",
"totalAttributedSales14d",
],
"PRODUCTS": [
"attributedDetailPageViewsClicks14d",
"attributedAddToCartClicks14d",
"attributedPurchases14d",
"unitsSold14d",
"attributedSales14d",
"brandHaloDetailPageViewsClicks14d",
"brandHaloAttributedAddToCartClicks14d",
"brandHaloAttributedPurchases14d",
"brandHaloUnitsSold14d",
"brandHaloAttributedSales14d",
"attributedNewToBrandPurchases14d",
"attributedNewToBrandUnitsSold14d",
"attributedNewToBrandSales14d",
"brandHaloNewToBrandPurchases14d",
"brandHaloNewToBrandUnitsSold14d",
"brandHaloNewToBrandSales14d",
],
}


class AttributionReport(AmazonAdsStream):
"""
This stream corresponds to Amazon Advertising API - Attribution Reports
https://advertising.amazon.com/API/docs/en-us/amazon-attribution-prod-3p/#/
"""

model = AttributionReportModel
primary_key = None
data_field = "reports"
page_size = 300

report_type = ""
metrics = ""
group_by = ""

_next_page_token_field = "cursorId"
_current_profile_id = ""

REPORT_DATE_FORMAT = "YYYYMMDD"
CONFIG_DATE_FORMAT = "YYYY-MM-DD"
REPORTING_PERIOD = 90

def __init__(self, config: Mapping[str, Any], *args, **kwargs):
self._start_date = config.get("start_date")
self._req_start_date = ""
self._req_end_date = ""

super().__init__(config, *args, **kwargs)

def _set_dates(self, profile: Profile):
new_start_date = pendulum.now(tz=profile.timezone).subtract(days=1).date()
new_end_date = pendulum.now(tz=profile.timezone).date()

if self._start_date:
new_start_date = max(self._start_date, new_end_date.subtract(days=self.REPORTING_PERIOD))

self._req_start_date = new_start_date.format(self.REPORT_DATE_FORMAT)
self._req_end_date = new_end_date.format(self.REPORT_DATE_FORMAT)

@property
def http_method(self) -> str:
return "POST"

def read_records(self, *args, **kvargs) -> Iterable[Mapping[str, Any]]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I finally solved my local issue by adding a few profile Ids, though our dev account doesn't have any attribute reports. So, earlier, under the secrets/spec.json, when the profiles is null or empty, it continues to fetch. Can you add an if-statement around the line to skip the read_records if _profiles is empty?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

current implementation

    def read_records(self, *args, **kvargs) -> Iterable[Mapping[str, Any]]:
        """
        Iterate through self._profiles list and send read all records for each profile.
        """
        for profile in self._profiles:
            self._set_dates(profile)
            self._current_profile_id = profile.profileId
            yield from super().read_records(*args, **kvargs)

if self._profiles is empty, it should not trigger any fetches

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you do me a favor? Can you unset the profiles attribute in secrets/config.json, see if you get an error by running the read command?

python main.py read --config secrets/config.json --catalog integration_tests/configured_catalog.json

{
  "client_id": "amzn1.application-oa2-client.client-id",
  "client_secret": "secret",
  "scope": "advertising::campaign_management",
  "refresh_token": "a very long token",
  "region": "NA"
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No error when profiles attribute is removed from config.json file.

When there are no user provided profiles, API call is made to fetch all profiles.

https://github.com/airbytehq/airbyte/blob/master/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/streams/profiles.py#L33-L48

Copy link
Contributor

@YiyangLi YiyangLi Sep 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the feedback, that's really helpful.

The acceptance test is not passed. And the reason is that not all profiles are authorized to fetch reports, and the read command doesn't fail gracefully, which blocks the read on other streams.

I read the codes again, looks like a profile is not independent of the others. In our dev test account, there are about 10 profiles fetched from the API, and only 4 of them are authorized to read the attribute reports.

If I pass just these 4 profiles, the tests are good. But if I include another, the read command fails on that profile and stops fetching reports on other profiles. And under the configured_catalog.json, if I shuffle the streams collection to fetch attribute reports first, the following streams will stop.

Similarly, if I set a collection of profiles to secrets/config.json, but one of the profiles is authorized, the same error is thrown and not handled gracefully.

Under a read command, we want to fetch as much data as possible, and reports from one profile shouldn't impact another profile or other streams. Can you adjust the code to satisfy that?

You may consider to use a stream slice to store profiles, and fetch reports for each profile in that slice. The GitHub connector provides a good example in fetching users in an org.

Let me know if you have a question.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you help me in providing a way to reproduce the error?

I tried running this command

python main.py read --config secrets/config.json --catalog integration_tests/configured_catalog.json

config.json

{
  "region": "NA",
  "client_id": "amzn1.application-oa2-client.",
  "start_date": "2022-09-13",
  "client_secret": "secret",
  "refresh_token": "secret",
  "report_wait_timeout": 30,
  "report_generation_max_retries": 5,
  "profiles": [1, 2]
}

It's returning 0 records for all the streams except profiles

{"type": "LOG", "log": {"level": "INFO", "message": "Read 3 records from profiles stream"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Finished syncing profiles"}}
{"type": "LOG", "log": {"level": "INFO", "message": "SourceAmazonAds runtimes:\nSyncing stream profiles 0:00:00.005063"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Syncing stream: sponsored_display_campaigns "}}
{"type": "LOG", "log": {"level": "INFO", "message": "Read 0 records from sponsored_display_campaigns stream"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Finished syncing sponsored_display_campaigns"}}
{"type": "LOG", "log": {"level": "INFO", "message": "SourceAmazonAds runtimes:\nSyncing stream profiles 0:00:00.005063\nSyncing stream sponsored_display_campaigns 0:00:00.000228"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Syncing stream: sponsored_display_ad_groups "}}
{"type": "LOG", "log": {"level": "INFO", "message": "Read 0 records from sponsored_display_ad_groups stream"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Finished syncing sponsored_display_ad_groups"}}
{"type": "LOG", "log": {"level": "INFO", "message": "SourceAmazonAds runtimes:\nSyncing stream profiles 0:00:00.005063\nSyncing stream sponsored_display_ad_groups 0:00:00.000135\nSyncing stream sponsored_display_campaigns 0:00:00.000228"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Syncing stream: sponsored_display_product_ads "}}
{"type": "LOG", "log": {"level": "INFO", "message": "Read 0 records from sponsored_display_product_ads stream"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Finished syncing sponsored_display_product_ads"}}
{"type": "LOG", "log": {"level": "INFO", "message": "SourceAmazonAds runtimes:\nSyncing stream profiles 0:00:00.005063\nSyncing stream sponsored_display_ad_groups 0:00:00.000135\nSyncing stream sponsored_display_campaigns 0:00:00.000228\nSyncing stream sponsored_display_product_ads 0:00:00.000272"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Syncing stream: sponsored_display_targetings "}}
{"type": "LOG", "log": {"level": "INFO", "message": "Read 0 records from sponsored_display_targetings stream"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Finished syncing sponsored_display_targetings"}}
{"type": "LOG", "log": {"level": "INFO", "message": "SourceAmazonAds runtimes:\nSyncing stream profiles 0:00:00.005063\nSyncing stream sponsored_display_ad_groups 0:00:00.000135\nSyncing stream sponsored_display_campaigns 0:00:00.000228\nSyncing stream sponsored_display_product_ads 0:00:00.000272\nSyncing stream sponsored_display_targetings 0:00:00.000179"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Syncing stream: sponsored_product_campaigns "}}
{"type": "LOG", "log": {"level": "INFO", "message": "Read 0 records from sponsored_product_campaigns stream"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Finished syncing sponsored_product_campaigns"}}
{"type": "LOG", "log": {"level": "INFO", "message": "SourceAmazonAds runtimes:\nSyncing stream profiles 0:00:00.005063\nSyncing stream sponsored_display_ad_groups 0:00:00.000135\nSyncing stream sponsored_display_campaigns 0:00:00.000228\nSyncing stream sponsored_display_product_ads 0:00:00.000272\nSyncing stream sponsored_display_targetings 0:00:00.000179\nSyncing stream sponsored_product_campaigns 0:00:00.000159"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Syncing stream: sponsored_product_ad_groups "}}
{"type": "LOG", "log": {"level": "INFO", "message": "Read 0 records from sponsored_product_ad_groups stream"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Finished syncing sponsored_product_ad_groups"}}
{"type": "LOG", "log": {"level": "INFO", "message": "SourceAmazonAds runtimes:\nSyncing stream profiles 0:00:00.005063\nSyncing stream sponsored_display_ad_groups 0:00:00.000135\nSyncing stream sponsored_display_campaigns 0:00:00.000228\nSyncing stream sponsored_display_product_ads 0:00:00.000272\nSyncing stream sponsored_display_targetings 0:00:00.000179\nSyncing stream sponsored_product_ad_groups 0:00:00.000161\nSyncing stream sponsored_product_campaigns 0:00:00.000159"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Syncing stream: sponsored_product_keywords "}}
{"type": "LOG", "log": {"level": "INFO", "message": "Read 0 records from sponsored_product_keywords stream"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Finished syncing sponsored_product_keywords"}}
{"type": "LOG", "log": {"level": "INFO", "message": "SourceAmazonAds runtimes:\nSyncing stream profiles 0:00:00.005063\nSyncing stream sponsored_display_ad_groups 0:00:00.000135\nSyncing stream sponsored_display_campaigns 0:00:00.000228\nSyncing stream sponsored_display_product_ads 0:00:00.000272\nSyncing stream sponsored_display_targetings 0:00:00.000179\nSyncing stream sponsored_product_ad_groups 0:00:00.000161\nSyncing stream sponsored_product_campaigns 0:00:00.000159\nSyncing stream sponsored_product_keywords 0:00:00.000143"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Syncing stream: sponsored_product_negative_keywords "}}
{"type": "LOG", "log": {"level": "INFO", "message": "Read 0 records from sponsored_product_negative_keywords stream"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Finished syncing sponsored_product_negative_keywords"}}
{"type": "LOG", "log": {"level": "INFO", "message": "SourceAmazonAds runtimes:\nSyncing stream profiles 0:00:00.005063\nSyncing stream sponsored_display_ad_groups 0:00:00.000135\nSyncing stream sponsored_display_campaigns 0:00:00.000228\nSyncing stream sponsored_display_product_ads 0:00:00.000272\nSyncing stream sponsored_display_targetings 0:00:00.000179\nSyncing stream sponsored_product_ad_groups 0:00:00.000161\nSyncing stream sponsored_product_campaigns 0:00:00.000159\nSyncing stream sponsored_product_keywords 0:00:00.000143\nSyncing stream sponsored_product_negative_keywords 0:00:00.000143"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Syncing stream: sponsored_product_ads "}}
{"type": "LOG", "log": {"level": "INFO", "message": "Read 0 records from sponsored_product_ads stream"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Finished syncing sponsored_product_ads"}}
{"type": "LOG", "log": {"level": "INFO", "message": "SourceAmazonAds runtimes:\nSyncing stream profiles 0:00:00.005063\nSyncing stream sponsored_display_ad_groups 0:00:00.000135\nSyncing stream sponsored_display_campaigns 0:00:00.000228\nSyncing stream sponsored_display_product_ads 0:00:00.000272\nSyncing stream sponsored_display_targetings 0:00:00.000179\nSyncing stream sponsored_product_ad_groups 0:00:00.000161\nSyncing stream sponsored_product_ads 0:00:00.000361\nSyncing stream sponsored_product_campaigns 0:00:00.000159\nSyncing stream sponsored_product_keywords 0:00:00.000143\nSyncing stream sponsored_product_negative_keywords 0:00:00.000143"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Syncing stream: sponsored_product_targetings "}}
{"type": "LOG", "log": {"level": "INFO", "message": "Read 0 records from sponsored_product_targetings stream"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Finished syncing sponsored_product_targetings"}}
{"type": "LOG", "log": {"level": "INFO", "message": "SourceAmazonAds runtimes:\nSyncing stream profiles 0:00:00.005063\nSyncing stream sponsored_display_ad_groups 0:00:00.000135\nSyncing stream sponsored_display_campaigns 0:00:00.000228\nSyncing stream sponsored_display_product_ads 0:00:00.000272\nSyncing stream sponsored_display_targetings 0:00:00.000179\nSyncing stream sponsored_product_ad_groups 0:00:00.000161\nSyncing stream sponsored_product_ads 0:00:00.000361\nSyncing stream sponsored_product_campaigns 0:00:00.000159\nSyncing stream sponsored_product_keywords 0:00:00.000143\nSyncing stream sponsored_product_negative_keywords 0:00:00.000143\nSyncing stream sponsored_product_targetings 0:00:00.000144"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Syncing stream: sponsored_brands_campaigns "}}
{"type": "LOG", "log": {"level": "INFO", "message": "Read 0 records from sponsored_brands_campaigns stream"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Finished syncing sponsored_brands_campaigns"}}
{"type": "LOG", "log": {"level": "INFO", "message": "SourceAmazonAds runtimes:\nSyncing stream profiles 0:00:00.005063\nSyncing stream sponsored_brands_campaigns 0:00:00.000144\nSyncing stream sponsored_display_ad_groups 0:00:00.000135\nSyncing stream sponsored_display_campaigns 0:00:00.000228\nSyncing stream sponsored_display_product_ads 0:00:00.000272\nSyncing stream sponsored_display_targetings 0:00:00.000179\nSyncing stream sponsored_product_ad_groups 0:00:00.000161\nSyncing stream sponsored_product_ads 0:00:00.000361\nSyncing stream sponsored_product_campaigns 0:00:00.000159\nSyncing stream sponsored_product_keywords 0:00:00.000143\nSyncing stream sponsored_product_negative_keywords 0:00:00.000143\nSyncing stream sponsored_product_targetings 0:00:00.000144"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Syncing stream: sponsored_brands_ad_groups "}}
{"type": "LOG", "log": {"level": "INFO", "message": "Read 0 records from sponsored_brands_ad_groups stream"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Finished syncing sponsored_brands_ad_groups"}}
{"type": "LOG", "log": {"level": "INFO", "message": "SourceAmazonAds runtimes:\nSyncing stream profiles 0:00:00.005063\nSyncing stream sponsored_brands_ad_groups 0:00:00.000145\nSyncing stream sponsored_brands_campaigns 0:00:00.000144\nSyncing stream sponsored_display_ad_groups 0:00:00.000135\nSyncing stream sponsored_display_campaigns 0:00:00.000228\nSyncing stream sponsored_display_product_ads 0:00:00.000272\nSyncing stream sponsored_display_targetings 0:00:00.000179\nSyncing stream sponsored_product_ad_groups 0:00:00.000161\nSyncing stream sponsored_product_ads 0:00:00.000361\nSyncing stream sponsored_product_campaigns 0:00:00.000159\nSyncing stream sponsored_product_keywords 0:00:00.000143\nSyncing stream sponsored_product_negative_keywords 0:00:00.000143\nSyncing stream sponsored_product_targetings 0:00:00.000144"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Syncing stream: sponsored_brands_keywords "}}
{"type": "LOG", "log": {"level": "INFO", "message": "Read 0 records from sponsored_brands_keywords stream"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Finished syncing sponsored_brands_keywords"}}
{"type": "LOG", "log": {"level": "INFO", "message": "SourceAmazonAds runtimes:\nSyncing stream profiles 0:00:00.005063\nSyncing stream sponsored_brands_ad_groups 0:00:00.000145\nSyncing stream sponsored_brands_campaigns 0:00:00.000144\nSyncing stream sponsored_brands_keywords 0:00:00.000170\nSyncing stream sponsored_display_ad_groups 0:00:00.000135\nSyncing stream sponsored_display_campaigns 0:00:00.000228\nSyncing stream sponsored_display_product_ads 0:00:00.000272\nSyncing stream sponsored_display_targetings 0:00:00.000179\nSyncing stream sponsored_product_ad_groups 0:00:00.000161\nSyncing stream sponsored_product_ads 0:00:00.000361\nSyncing stream sponsored_product_campaigns 0:00:00.000159\nSyncing stream sponsored_product_keywords 0:00:00.000143\nSyncing stream sponsored_product_negative_keywords 0:00:00.000143\nSyncing stream sponsored_product_targetings 0:00:00.000144"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Syncing stream: attribution_report_products "}}
{"type": "LOG", "log": {"level": "INFO", "message": "Read 0 records from attribution_report_products stream"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Finished syncing attribution_report_products"}}
{"type": "LOG", "log": {"level": "INFO", "message": "SourceAmazonAds runtimes:\nSyncing stream attribution_report_products 0:00:00.007100\nSyncing stream profiles 0:00:00.005063\nSyncing stream sponsored_brands_ad_groups 0:00:00.000145\nSyncing stream sponsored_brands_campaigns 0:00:00.000144\nSyncing stream sponsored_brands_keywords 0:00:00.000170\nSyncing stream sponsored_display_ad_groups 0:00:00.000135\nSyncing stream sponsored_display_campaigns 0:00:00.000228\nSyncing stream sponsored_display_product_ads 0:00:00.000272\nSyncing stream sponsored_display_targetings 0:00:00.000179\nSyncing stream sponsored_product_ad_groups 0:00:00.000161\nSyncing stream sponsored_product_ads 0:00:00.000361\nSyncing stream sponsored_product_campaigns 0:00:00.000159\nSyncing stream sponsored_product_keywords 0:00:00.000143\nSyncing stream sponsored_product_negative_keywords 0:00:00.000143\nSyncing stream sponsored_product_targetings 0:00:00.000144"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Syncing stream: attribution_report_performance_adgroup "}}
{"type": "LOG", "log": {"level": "INFO", "message": "Read 0 records from attribution_report_performance_adgroup stream"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Finished syncing attribution_report_performance_adgroup"}}
{"type": "LOG", "log": {"level": "INFO", "message": "SourceAmazonAds runtimes:\nSyncing stream attribution_report_performance_adgroup 0:00:00.000172\nSyncing stream attribution_report_products 0:00:00.007100\nSyncing stream profiles 0:00:00.005063\nSyncing stream sponsored_brands_ad_groups 0:00:00.000145\nSyncing stream sponsored_brands_campaigns 0:00:00.000144\nSyncing stream sponsored_brands_keywords 0:00:00.000170\nSyncing stream sponsored_display_ad_groups 0:00:00.000135\nSyncing stream sponsored_display_campaigns 0:00:00.000228\nSyncing stream sponsored_display_product_ads 0:00:00.000272\nSyncing stream sponsored_display_targetings 0:00:00.000179\nSyncing stream sponsored_product_ad_groups 0:00:00.000161\nSyncing stream sponsored_product_ads 0:00:00.000361\nSyncing stream sponsored_product_campaigns 0:00:00.000159\nSyncing stream sponsored_product_keywords 0:00:00.000143\nSyncing stream sponsored_product_negative_keywords 0:00:00.000143\nSyncing stream sponsored_product_targetings 0:00:00.000144"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Syncing stream: attribution_report_performance_campaign "}}
{"type": "LOG", "log": {"level": "INFO", "message": "Read 0 records from attribution_report_performance_campaign stream"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Finished syncing attribution_report_performance_campaign"}}
{"type": "LOG", "log": {"level": "INFO", "message": "SourceAmazonAds runtimes:\nSyncing stream attribution_report_performance_adgroup 0:00:00.000172\nSyncing stream attribution_report_performance_campaign 0:00:00.000294\nSyncing stream attribution_report_products 0:00:00.007100\nSyncing stream profiles 0:00:00.005063\nSyncing stream sponsored_brands_ad_groups 0:00:00.000145\nSyncing stream sponsored_brands_campaigns 0:00:00.000144\nSyncing stream sponsored_brands_keywords 0:00:00.000170\nSyncing stream sponsored_display_ad_groups 0:00:00.000135\nSyncing stream sponsored_display_campaigns 0:00:00.000228\nSyncing stream sponsored_display_product_ads 0:00:00.000272\nSyncing stream sponsored_display_targetings 0:00:00.000179\nSyncing stream sponsored_product_ad_groups 0:00:00.000161\nSyncing stream sponsored_product_ads 0:00:00.000361\nSyncing stream sponsored_product_campaigns 0:00:00.000159\nSyncing stream sponsored_product_keywords 0:00:00.000143\nSyncing stream sponsored_product_negative_keywords 0:00:00.000143\nSyncing stream sponsored_product_targetings 0:00:00.000144"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Syncing stream: attribution_report_performance_creative "}}
{"type": "LOG", "log": {"level": "INFO", "message": "Read 0 records from attribution_report_performance_creative stream"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Finished syncing attribution_report_performance_creative"}}
{"type": "LOG", "log": {"level": "INFO", "message": "SourceAmazonAds runtimes:\nSyncing stream attribution_report_performance_adgroup 0:00:00.000172\nSyncing stream attribution_report_performance_campaign 0:00:00.000294\nSyncing stream attribution_report_performance_creative 0:00:00.007223\nSyncing stream attribution_report_products 0:00:00.007100\nSyncing stream profiles 0:00:00.005063\nSyncing stream sponsored_brands_ad_groups 0:00:00.000145\nSyncing stream sponsored_brands_campaigns 0:00:00.000144\nSyncing stream sponsored_brands_keywords 0:00:00.000170\nSyncing stream sponsored_display_ad_groups 0:00:00.000135\nSyncing stream sponsored_display_campaigns 0:00:00.000228\nSyncing stream sponsored_display_product_ads 0:00:00.000272\nSyncing stream sponsored_display_targetings 0:00:00.000179\nSyncing stream sponsored_product_ad_groups 0:00:00.000161\nSyncing stream sponsored_product_ads 0:00:00.000361\nSyncing stream sponsored_product_campaigns 0:00:00.000159\nSyncing stream sponsored_product_keywords 0:00:00.000143\nSyncing stream sponsored_product_negative_keywords 0:00:00.000143\nSyncing stream sponsored_product_targetings 0:00:00.000144"}}
{"type": "LOG", "log": {"level": "INFO", "message": "Finished syncing SourceAmazonAds"}}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, be happy to!

firstly, check integration_tests/configured_catalog.json, if profiles is one of the streams, the output will include the profile detail.

"Read 3 records from profiles stream"

looks like you have 3 profiles.

I guess all 3 profiles are authorized to read attribute_reports_*. Can you create another profile that is not authorized and add it to the array of profiles in secrets/config.json? For our case, if one of profiles in the array is not authorized, we will get the error of This profileID is not authorized to use Amazon Attribution, and the read stops.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any ideas on why I am not getting any error with fake profile IDs in config.json

{
  "region": "NA",
  "client_id": "amzn1.application-oa2-client.",
  "start_date": "2022-09-13",
  "client_secret": "secret",
  "refresh_token": "secret",
  "report_wait_timeout": 30,
  "report_generation_max_retries": 5,
  "profiles": [1, 2]
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure, maybe on the api side, the validation handles it before the authorization module is run?

"""
Iterate through self._profiles list and send read all records for each profile.
"""
for profile in self._profiles:
self._set_dates(profile)
self._current_profile_id = profile.profileId
yield from super().read_records(*args, **kvargs)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try:
    self._set_dates(profile)
    self._current_profile_id = profile.profileId
    yield from super().read_records(*args, **kvargs)
except Exception:
    # This profile doesn't have any records/access
    self.logger.info(
        "This profile doesn't have any records/access")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@harshithmullapudi

Have implemented above changes. Updated the unit tests also. PTAL

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot.


def request_headers(self, *args, **kvargs) -> MutableMapping[str, Any]:
headers = super().request_headers(*args, **kvargs)
headers["Amazon-Advertising-API-Scope"] = str(self._current_profile_id)
return headers

def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]:
stream_data = response.json()
next_page_token = stream_data.get(self._next_page_token_field)
if next_page_token:
return {self._next_page_token_field: next_page_token}

def path(self, **kvargs) -> str:
return "/attribution/report"

def request_body_json(
self,
stream_state: Mapping[str, Any],
stream_slice: Mapping[str, Any] = None,
next_page_token: Mapping[str, Any] = None,
) -> Optional[Mapping]:
body = {
"reportType": self.report_type,
"count": self.page_size,
"metrics": self.metrics,
"startDate": self._req_start_date,
"endDate": self._req_end_date,
self._next_page_token_field: "",
}

if self.group_by:
body["groupBy"] = self.group_by

if next_page_token:
body[self._next_page_token_field] = next_page_token[self._next_page_token_field]

return body


class AttributionReportProducts(AttributionReport):
report_type = "PRODUCTS"

metrics = ",".join(METRICS_MAP[report_type])

group_by = ""


class AttributionReportPerformanceCreative(AttributionReport):
report_type = "PERFORMANCE"

metrics = ",".join(METRICS_MAP[report_type])

group_by = "CREATIVE"


class AttributionReportPerformanceAdgroup(AttributionReport):
report_type = "PERFORMANCE"

metrics_list = METRICS_MAP[report_type]
metrics_list.append(BRAND_REFERRAL_BONUS)
metrics = ",".join(metrics_list)

group_by = "ADGROUP"


class AttributionReportPerformanceCampaign(AttributionReport):
report_type = "PERFORMANCE"

metrics_list = METRICS_MAP[report_type]
metrics_list.append(BRAND_REFERRAL_BONUS)
metrics = ",".join(metrics_list)

group_by = "CAMPAIGN"
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ class AmazonAdsStream(HttpStream, BasicAmazonAdsStream):
Class for getting data from streams that based on single http request.
"""

data_field = ""

def __init__(self, config: Mapping[str, Any], *args, profiles: List[Profile] = None, **kwargs):
# Each AmazonAdsStream instance are dependant on list of profiles.
BasicAmazonAdsStream.__init__(self, config, profiles=profiles)
Expand All @@ -121,7 +123,10 @@ def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapp
:return an object representing single record in the response
"""
if response.status_code == HTTPStatus.OK:
yield from response.json()
if self.data_field:
yield from response.json().get(self.data_field, [])
else:
yield from response.json()
return

"""
Expand Down
Loading