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

Adding "Customers" stream #49

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 14 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 4 additions & 3 deletions meltano.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tap_googleads/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
108 changes: 90 additions & 18 deletions tap_googleads/schemas/campaign.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
}
}
47 changes: 45 additions & 2 deletions tap_googleads/streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""

Expand Down Expand Up @@ -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
"""

Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions tap_googleads/tap.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from tap_googleads.streams import (
CampaignsStream,
CustomerStream,
AdGroupsStream,
AdGroupsPerformance,
AccessibleCustomers,
Expand All @@ -23,6 +24,7 @@

STREAM_TYPES = [
CampaignsStream,
CustomerStream,
AdGroupsStream,
AdGroupsPerformance,
AccessibleCustomers,
Expand Down
2 changes: 1 addition & 1 deletion tap_googleads/tests/test_customer_not_found.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down