Skip to content

Commit ab6cc49

Browse files
🎉 Source Google Ads: add new stream 'click_view' (#6684)
* fetch gclid from clickview stream * resolve comments and update google ads document * add document url in stream.py file * fixed date range issue and added campaign name in json schema Co-authored-by: blotouta2 <ankur@blotout.io>
1 parent 00d56e7 commit ab6cc49

File tree

10 files changed

+93
-11
lines changed

10 files changed

+93
-11
lines changed

airbyte-config/init/src/main/resources/config/STANDARD_SOURCE_DEFINITION/253487c0-2246-43ba-a21f-5116b20a2c50.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
"sourceDefinitionId": "253487c0-2246-43ba-a21f-5116b20a2c50",
33
"name": "Google Ads",
44
"dockerRepository": "airbyte/source-google-ads",
5-
"dockerImageTag": "0.1.11",
5+
"dockerImageTag": "0.1.15",
66
"documentationUrl": "https://docs.airbyte.io/integrations/sources/google-ads"
77
}

airbyte-config/init/src/main/resources/seed/source_definitions.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
- sourceDefinitionId: 253487c0-2246-43ba-a21f-5116b20a2c50
3434
name: Google Ads
3535
dockerRepository: airbyte/source-google-ads
36-
dockerImageTag: 0.1.14
36+
dockerImageTag: 0.1.15
3737
documentationUrl: https://docs.airbyte.io/integrations/sources/google-ads
3838
sourceType: api
3939
- sourceDefinitionId: ef69ef6e-aa7f-4af1-a01d-ef775033524e

airbyte-integrations/connectors/source-google-ads/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ RUN pip install .
1313

1414
ENTRYPOINT ["python", "/airbyte/integration_code/main.py"]
1515

16-
LABEL io.airbyte.version=0.1.14
16+
LABEL io.airbyte.version=0.1.15
1717
LABEL io.airbyte.name=airbyte/source-google-ads

airbyte-integrations/connectors/source-google-ads/integration_tests/configured_catalog.json

+12
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@
2424
"destination_sync_mode": "overwrite",
2525
"cursor_field": ["segments.date"]
2626
},
27+
{
28+
"stream": {
29+
"name": "click_view",
30+
"json_schema": {},
31+
"supported_sync_modes": ["full_refresh", "incremental"],
32+
"source_defined_cursor": true,
33+
"default_cursor_field": ["segments.date"]
34+
},
35+
"sync_mode": "incremental",
36+
"destination_sync_mode": "overwrite",
37+
"cursor_field": ["segments.date"]
38+
},
2739
{
2840
"stream": {
2941
"name": "display_keyword_performance_report",

airbyte-integrations/connectors/source-google-ads/integration_tests/configured_catalog_without_empty_streams.json

+12
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@
5454
"sync_mode": "full_refresh",
5555
"destination_sync_mode": "overwrite"
5656
},
57+
{
58+
"stream": {
59+
"name": "click_view",
60+
"json_schema": {},
61+
"supported_sync_modes": ["full_refresh", "incremental"],
62+
"source_defined_cursor": true,
63+
"default_cursor_field": ["segments.date"]
64+
},
65+
"sync_mode": "incremental",
66+
"destination_sync_mode": "overwrite",
67+
"cursor_field": ["segments.date"]
68+
},
5769
{
5870
"stream": {
5971
"name": "campaigns",

airbyte-integrations/connectors/source-google-ads/source_google_ads/google_ads.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from enum import Enum
77
from typing import Any, List, Mapping
88

9+
import pendulum
910
from google.ads.googleads.client import GoogleAdsClient
1011
from google.ads.googleads.v8.services.types.google_ads_service import GoogleAdsRow, SearchGoogleAdsResponse
1112
from proto.marshal.collections import Repeated, RepeatedComposite
@@ -21,6 +22,7 @@
2122
"display_topics_performance_report": "topic_view",
2223
"shopping_performance_report": "shopping_performance_view",
2324
"user_location_report": "user_location_view",
25+
"click_view": "click_view",
2426
}
2527

2628

@@ -79,8 +81,10 @@ def convert_schema_into_query(
7981
query_template = f"SELECT {fields} FROM {from_category} "
8082

8183
if cursor_field:
82-
# Fix issue 5411: Make date_start and date_end inclusive.
83-
query_template += f"WHERE {cursor_field} >= '{from_date}' AND {cursor_field} <= '{to_date}' ORDER BY {cursor_field} ASC"
84+
end_date_inclusive = "<=" if (pendulum.parse(to_date) - pendulum.parse(from_date)).days > 1 else "<"
85+
query_template += (
86+
f"WHERE {cursor_field} >= '{from_date}' AND {cursor_field} {end_date_inclusive} '{to_date}' ORDER BY {cursor_field} ASC"
87+
)
8488

8589
return query_template
8690

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"type": "object",
4+
"properties": {
5+
"click_view.gclid": {
6+
"type": ["null", "string"]
7+
},
8+
"campaign.id": {
9+
"type": ["null", "integer"]
10+
},
11+
"ad_group.id": {
12+
"type": ["null", "integer"]
13+
},
14+
"segments.date": {
15+
"type": ["null", "string"]
16+
},
17+
"customer.id": {
18+
"type": ["null", "integer"]
19+
},
20+
"campaign.name": {
21+
"type": ["null", "string"]
22+
}
23+
}
24+
}

airbyte-integrations/connectors/source-google-ads/source_google_ads/source.py

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
AdGroupAds,
2121
AdGroups,
2222
Campaigns,
23+
ClickView,
2324
DisplayKeywordPerformanceReport,
2425
DisplayTopicsPerformanceReport,
2526
ShoppingPerformanceReport,
@@ -74,4 +75,5 @@ def streams(self, config: Mapping[str, Any]) -> List[Stream]:
7475
AdGroups(api=google_api),
7576
Accounts(api=google_api),
7677
Campaigns(api=google_api),
78+
ClickView(**incremental_stream_config),
7779
] + custom_query_streams

airbyte-integrations/connectors/source-google-ads/source_google_ads/streams.py

+29-6
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
from .google_ads import GoogleAds
1313

1414

15-
def chunk_date_range(start_date: str, conversion_window: int, field: str, end_date: str = None) -> Iterable[Mapping[str, any]]:
15+
def chunk_date_range(
16+
start_date: str, conversion_window: int, field: str, end_date: str = None, time_unit: str = "months", days_of_data_storage: int = None
17+
) -> Iterable[Mapping[str, any]]:
1618
"""
1719
Passing optional parameter end_date for testing
1820
Returns a list of the beginning and ending timetsamps of each month between the start date and now.
@@ -22,6 +24,10 @@ def chunk_date_range(start_date: str, conversion_window: int, field: str, end_da
2224
end_date = pendulum.parse(end_date) if end_date else pendulum.now()
2325
start_date = pendulum.parse(start_date)
2426

27+
# For some metrics we can only get data not older than N days, it is Google Ads policy
28+
if days_of_data_storage:
29+
start_date = max(start_date, pendulum.now().subtract(days=days_of_data_storage - conversion_window))
30+
2531
# As in to return some state when state in abnormal
2632
if start_date > end_date:
2733
return [{field: start_date.to_date_string()}]
@@ -32,7 +38,7 @@ def chunk_date_range(start_date: str, conversion_window: int, field: str, end_da
3238
# Each stream_slice contains the beginning and ending timestamp for a 24 hour period
3339
while start_date < end_date:
3440
intervals.append({field: start_date.to_date_string()})
35-
start_date = start_date.add(months=1)
41+
start_date = start_date.add(**{time_unit: 1})
3642

3743
return intervals
3844

@@ -55,8 +61,10 @@ def read_records(self, sync_mode, stream_slice: Mapping[str, Any] = None, **kwar
5561

5662

5763
class IncrementalGoogleAdsStream(GoogleAdsStream, ABC):
64+
days_of_data_storage = None
5865
cursor_field = "segments.date"
5966
primary_key = None
67+
time_unit = "months"
6068

6169
def __init__(self, start_date: str, conversion_window_days: int, **kwargs):
6270
self.conversion_window_days = conversion_window_days
@@ -67,16 +75,22 @@ def stream_slices(self, stream_state: Mapping[str, Any] = None, **kwargs) -> Ite
6775
stream_state = stream_state or {}
6876
start_date = stream_state.get(self.cursor_field) or self._start_date
6977

70-
return chunk_date_range(start_date=start_date, conversion_window=self.conversion_window_days, field=self.cursor_field)
78+
return chunk_date_range(
79+
start_date=start_date,
80+
conversion_window=self.conversion_window_days,
81+
field=self.cursor_field,
82+
time_unit=self.time_unit,
83+
days_of_data_storage=self.days_of_data_storage,
84+
)
7185

7286
@staticmethod
73-
def get_date_params(stream_slice: Mapping[str, Any], cursor_field: str, end_date: pendulum.datetime = None):
87+
def get_date_params(stream_slice: Mapping[str, Any], cursor_field: str, end_date: pendulum.datetime = None, time_unit: str = "months"):
7488
end_date = end_date or pendulum.yesterday()
7589
start_date = pendulum.parse(stream_slice.get(cursor_field))
7690
if start_date > pendulum.now():
7791
return start_date.to_date_string(), start_date.add(days=1).to_date_string()
7892

79-
end_date = min(end_date, pendulum.parse(stream_slice.get(cursor_field)).add(months=1))
93+
end_date = min(end_date, pendulum.parse(stream_slice.get(cursor_field)).add(**{time_unit: 1}))
8094

8195
# Fix issue #4806, start date should always be lower than end date.
8296
if start_date.add(days=1).date() >= end_date.date():
@@ -100,7 +114,7 @@ def get_updated_state(self, current_stream_state: MutableMapping[str, Any], late
100114
return current_stream_state
101115

102116
def get_query(self, stream_slice: Mapping[str, Any] = None) -> str:
103-
start_date, end_date = self.get_date_params(stream_slice, self.cursor_field)
117+
start_date, end_date = self.get_date_params(stream_slice, self.cursor_field, time_unit=self.time_unit)
104118
query = GoogleAds.convert_schema_into_query(
105119
schema=self.get_json_schema(), report_name=self.name, from_date=start_date, to_date=end_date, cursor_field=self.cursor_field
106120
)
@@ -179,3 +193,12 @@ class UserLocationReport(IncrementalGoogleAdsStream):
179193
UserLocationReport stream: https://developers.google.com/google-ads/api/fields/v8/user_location_view
180194
Google Ads API field mapping: https://developers.google.com/google-ads/api/docs/migration/mapping#geo_performance
181195
"""
196+
197+
198+
class ClickView(IncrementalGoogleAdsStream):
199+
"""
200+
ClickView stream: https://developers.google.com/google-ads/api/reference/rpc/v8/ClickView
201+
"""
202+
203+
time_unit = "days"
204+
days_of_data_storage = 90

docs/integrations/sources/google-ads.md

+5
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ This source is capable of syncing the following tables and their data:
2222
* [ad_group_ads](https://developers.google.com/google-ads/api/fields/v8/ad_group_ad)
2323
* [ad_groups](https://developers.google.com/google-ads/api/fields/v8/ad_group)
2424
* [campaigns](https://developers.google.com/google-ads/api/fields/v8/campaign)
25+
* [click_view](https://developers.google.com/google-ads/api/reference/rpc/v8/ClickView)
2526

2627
#### Report Tables
2728
* [account_performance_report](https://developers.google.com/google-ads/api/docs/migration/mapping#account_performance)
@@ -30,6 +31,8 @@ This source is capable of syncing the following tables and their data:
3031
* [display_topics_report](https://developers.google.com/google-ads/api/docs/migration/mapping#display_topics_performance)
3132
* [shopping_performance_report](https://developers.google.com/google-ads/api/docs/migration/mapping#shopping_performance)
3233

34+
**Note**: Due to constraints from the Google Ads API, the `click_view` stream retrieves data one day at a time and can only retrieve data newer than 90 days ago
35+
3336
## Getting Started (Airbyte-Cloud)
3437

3538
1. Click `Authenticate your Google Ads account` to sign in with Google and authorize your account.
@@ -91,6 +94,8 @@ This source is constrained by whatever API limits are set for the Google Ads tha
9194

9295
| Version | Date | Pull Request | Subject |
9396
| :------ | :-------- | :----- | :------ |
97+
| `0.1.15` | 2021-10-07 | [6684](https://github.com/airbytehq/airbyte/pull/6684) | Add new stream `click_view` |
98+
| `0.1.14` | 2021-10-01 | [6565](https://github.com/airbytehq/airbyte/pull/6565) | Fix OAuth Spec File |
9499
| `0.1.13` | 2021-09-27 | [6458](https://github.com/airbytehq/airbyte/pull/6458) | Update OAuth Spec File |
95100
| `0.1.11` | 2021-09-22 | [#6373](https://github.com/airbytehq/airbyte/pull/6373) | Fix inconsistent segments.date field type across all streams |
96101
| `0.1.10` | 2021-09-13 | [#6022](https://github.com/airbytehq/airbyte/pull/6022) | Annotate Oauth2 flow initialization parameters in connector spec |

0 commit comments

Comments
 (0)