diff --git a/README.md b/README.md index 599ca89..ba9a05a 100644 --- a/README.md +++ b/README.md @@ -27,18 +27,24 @@ THIS IS NOT READY FOR PRODUCTION. Bearer tokens sometimes slip out to logs. Use | end_date | True | 2022-03-31T00:00:00Z (Today) | Date to end our search on, applies to Streams where there is a filter date. Note that the query is BETWEEN start_date AND end_date | ### Get refresh token -1. GET https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=client_id&redirect_uri=http://127.0.0.1&scope=https://www.googleapis.com/auth/adwords&state=autoidm&access_type=offline&prompt=select_account&include_granted_scopes=true -1. POST https://www.googleapis.com/oauth2/v4/token?code={code}&client_id={client_id}&client_secret={client_secret}&redirect_uri=http://127.0.0.1&grant_type=authorization_code -1. POST https://www.googleapis.com/oauth2/v4/token?refresh_token={refres_token}&client_id={client_id}&client_secret={client_secret]&grant_type=refresh_token +Get a developer token, client id, and client secret as recommended in +the [Google Ads API documentation](https://developers.google.com/google-ads/api/docs/first-call/overview). +Then do the following: +1. Modify following template url and put it in a browser. Then follow the prompts to authenticate. +`GET https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id={client_id}&redirect_uri=http://127.0.0.1&scope=https://www.googleapis.com/auth/adwords&access_type=offline&prompt=select_account&include_granted_scopes=true` -## Installation -```bash -pipx install tap-googleads -``` +2. Modify the following url with the value in the `code` parameter that was returned in the previous browser url bar. Replace all other templated values and submit a `POST` request: +`https://www.googleapis.com/oauth2/v4/token?code={code}&client_id={client_id}&client_secret={client_secret}&redirect_uri=http://127.0.0.1&grant_type=authorization_code` + + +3. From the response you can grab the `refresh_token` and use it as is. Sometimes the following step is also required. + + +4. (IF NECESSARY) Modify the following templated url as suggested and submit a POST request. +`https://www.googleapis.com/oauth2/v4/token?refresh_token={refres_token}&client_id={client_id}&client_secret={client_secret]&grant_type=refresh_token` -## Configuration ### Accepted Config Options diff --git a/meltano.yml b/meltano.yml index 7e34476..d5bd96b 100644 --- a/meltano.yml +++ b/meltano.yml @@ -22,9 +22,10 @@ plugins: kind: password - name: login_customer_id kind: password - config: - start_date: "2020-01-01T00:00:00Z" - end_date: "2021-03-31T00:00:00Z" + - name: start_date + kind: string + - name: end_date + kind: string loaders: - name: target-jsonl variant: andyh1203 diff --git a/tap_googleads/client.py b/tap_googleads/client.py index 20c85c2..c84d0e2 100644 --- a/tap_googleads/client.py +++ b/tap_googleads/client.py @@ -20,7 +20,7 @@ class GoogleAdsStream(RESTStream): """GoogleAds stream class.""" - url_base = "https://googleads.googleapis.com/v11" + url_base = "https://googleads.googleapis.com/v12" records_jsonpath = "$[*]" # Or override `parse_response`. next_page_token_jsonpath = "$.nextPageToken" # Or override `get_next_page_token`. diff --git a/tap_googleads/schemas/campaign.json b/tap_googleads/schemas/campaign.json index e0d0f39..aae6db4 100644 --- a/tap_googleads/schemas/campaign.json +++ b/tap_googleads/schemas/campaign.json @@ -1,22 +1,94 @@ { - "type": "object", - "properties": { - "_sdc_primary_key": { - "type": "string" - }, - "campaign": { - "type": "object", - "properties": { - "resourceName": { - "type": "string" - }, - "name": { - "type": "string" - }, - "id": { - "type": "string" - } - } + "type": "object", + "properties": { + "_sdc_primary_key": { + "type": "string" + }, + "campaign": { + "type": "object", + "properties": { + "resourceName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "id": { + "type": "string" + }, + "status": { + "type": "string" + }, + "accessibleBiddingStrategy": { + "type": "string" + }, + "adServingOptimizationStatus": { + "type": "string" + }, + "advertisingChannelSubType": { + "type": "string" + }, + "advertisingChannelType": { + "type": "string" + }, + "baseCampaign": { + "type": "string" + }, + "biddingStrategy": { + "type": "string" + }, + "biddingStrategyType": { + "type": "string" + }, + "campaignBudget": { + "type": "string" + }, + "endDate": { + "type": "string" + }, + "excludedParentAssetFieldTypes": { + "type": "array" + }, + "experimentType": { + "type": "string" + }, + "finalUrlSuffix": { + "type": "string" + }, + "frequencyCaps": { + "type": "array" + }, + "labels": { + "type": "array" + }, + "paymentMode": { + "type": "string" + }, + "servingStatus": { + "type": "string" + }, + "startDate": { + "type": "string" + }, + "trackingUrlTemplate": { + "type": "string" + }, + "urlCustomParameters": { + "type": "array" + }, + "videoBrandSafetySuitability": { + "type": "string" + }, + "manualCpm": { + "type": "object" + }, + "manualCpv": { + "type": "object" + }, + "targetCpm": { + "type": "object" } + } } + } } diff --git a/tap_googleads/streams.py b/tap_googleads/streams.py index 1ed089b..006a653 100644 --- a/tap_googleads/streams.py +++ b/tap_googleads/streams.py @@ -12,6 +12,19 @@ SCHEMAS_DIR = Path(__file__).parent / Path("./schemas") +class CustomerStream(GoogleAdsStream): + """Define custom stream.""" + + @property + def path(self): + return "/customers/" + self.config["customer_id"] + + name = "customers" + primary_keys = ["id"] + replication_key = None + schema_filepath = SCHEMAS_DIR / "customer.json" + + class AccessibleCustomers(GoogleAdsStream): """Accessible Customers""" @@ -202,7 +215,32 @@ def gaql(self): return """ SELECT campaign.id , campaign.name - FROM campaign + , campaign.accessible_bidding_strategy + , campaign.ad_serving_optimization_status + , campaign.advertising_channel_sub_type + , campaign.advertising_channel_type + , campaign.base_campaign + , campaign.bidding_strategy + , campaign.bidding_strategy_type + , campaign.campaign_budget + , campaign.end_date + , campaign.excluded_parent_asset_field_types + , campaign.experiment_type + , campaign.final_url_suffix + , campaign.frequency_caps + , campaign.labels + , campaign.manual_cpm + , campaign.manual_cpv + , campaign.payment_mode + , campaign.resource_name + , campaign.serving_status + , campaign.start_date + , campaign.status + , campaign.target_cpm + , campaign.tracking_url_template + , campaign.url_custom_parameters + , campaign.video_brand_safety_suitability + FROM campaign ORDER BY campaign.id """ @@ -304,7 +342,11 @@ def gaql(self) -> str: records_jsonpath = "$.results[*]" name = "campaign_performance" - primary_keys_jsonpaths = ["campaign.resourceName", "segments.date"] + primary_keys_jsonpaths = [ + "campaign.resourceName", + "segments.date", + "segments.device", + ] primary_keys = ["_sdc_primary_key"] replication_key = None schema_filepath = SCHEMAS_DIR / "campaign_performance.json" @@ -447,6 +489,7 @@ def gaql(self) -> str: "campaign.resourceName", "locationView.resourceName", "segments.date", + "segments.conversion_action_category", ] primary_keys = ["_sdc_primary_key"] replication_key = None diff --git a/tap_googleads/tap.py b/tap_googleads/tap.py index 57d99bb..478cc89 100644 --- a/tap_googleads/tap.py +++ b/tap_googleads/tap.py @@ -8,6 +8,7 @@ from tap_googleads.streams import ( CampaignsStream, + CustomerStream, AdGroupsStream, AdGroupsPerformance, AccessibleCustomers, @@ -23,6 +24,7 @@ STREAM_TYPES = [ CampaignsStream, + CustomerStream, AdGroupsStream, AdGroupsPerformance, AccessibleCustomers, diff --git a/tap_googleads/tests/test_customer_not_found.py b/tap_googleads/tests/test_customer_not_found.py index cbc12f9..e1657ac 100644 --- a/tap_googleads/tests/test_customer_not_found.py +++ b/tap_googleads/tests/test_customer_not_found.py @@ -69,7 +69,7 @@ def test_customer_not_enabled(mocked_responses): mocked_responses.add( responses.POST, # TODO cleanup long url, googleads.googleapis.com/* would suffice - "https://googleads.googleapis.com/v11/customers/12345/googleAds:search?pageSize=10000&query=%0A%20%20%20%20%20%20%20%20%20%20%20%20SELECT%20campaign.id%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.name%0A%20%20%20%20%20%20%20%20%20%20%20%20FROM%20campaign%20%0A%20%20%20%20%20%20%20%20%20%20%20%20ORDER%20BY%20campaign.id%0A%20%20%20%20%20%20%20%20", + "https://googleads.googleapis.com/v11/customers/12345/googleAds:search?pageSize=10000&query=%0A%20%20%20%20%20%20%20%20%20%20%20%20SELECT%20campaign.id%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.name%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.accessible_bidding_strategy%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.ad_serving_optimization_status%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.advertising_channel_sub_type%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.advertising_channel_type%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.base_campaign%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.bidding_strategy%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.bidding_strategy_type%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.campaign_budget%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.end_date%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.excluded_parent_asset_field_types%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.experiment_type%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.final_url_suffix%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.frequency_caps%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.labels%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.manual_cpm%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.manual_cpv%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.payment_mode%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.resource_name%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.serving_status%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.start_date%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.status%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.target_cpm%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.tracking_url_template%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.url_custom_parameters%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20,%20campaign.video_brand_safety_suitability%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20FROM%20campaign%20%0A%20%20%20%20%20%20%20%20%20%20%20%20ORDER%20BY%20campaign.id%0A%20%20%20%20%20%20%20%20", body=json.dumps(customer_not_enabled_body).encode("utf-8"), status=403, content_type="application/json",