Skip to content

Commit a8f7365

Browse files
🎉 Source Facebook Marketing: add activities stream (#10655)
* add facebook marketing activities stream * update incremental test * add overrides for activities specific logic * formatting * update readme docs * remove test limitation * update dockerfile airbyte version * correct tests * bump connector version in config module Co-authored-by: marcosmarxm <marcosmarxm@gmail.com>
1 parent df47987 commit a8f7365

File tree

11 files changed

+110
-6
lines changed

11 files changed

+110
-6
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@
203203
- name: Facebook Marketing
204204
sourceDefinitionId: e7778cfc-e97c-4458-9ecb-b4f2bba8946c
205205
dockerRepository: airbyte/source-facebook-marketing
206-
dockerImageTag: 0.2.36
206+
dockerImageTag: 0.2.37
207207
documentationUrl: https://docs.airbyte.io/integrations/sources/facebook-marketing
208208
icon: facebook.svg
209209
sourceType: api

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1648,7 +1648,7 @@
16481648
supportsNormalization: false
16491649
supportsDBT: false
16501650
supported_destination_sync_modes: []
1651-
- dockerImage: "airbyte/source-facebook-marketing:0.2.36"
1651+
- dockerImage: "airbyte/source-facebook-marketing:0.2.37"
16521652
spec:
16531653
documentationUrl: "https://docs.airbyte.io/integrations/sources/facebook-marketing"
16541654
changelogUrl: "https://docs.airbyte.io/integrations/sources/facebook-marketing"

airbyte-integrations/connectors/source-facebook-marketing/Dockerfile

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,5 @@ RUN pip install .
1212
ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py"
1313
ENTRYPOINT ["python", "/airbyte/integration_code/main.py"]
1414

15-
16-
LABEL io.airbyte.version=0.2.36
15+
LABEL io.airbyte.version=0.2.37
1716
LABEL io.airbyte.name=airbyte/source-facebook-marketing

airbyte-integrations/connectors/source-facebook-marketing/integration_tests/configured_catalog_without_insights.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,19 @@
8585
"cursor_field": null,
8686
"destination_sync_mode": "append",
8787
"primary_key": null
88+
},
89+
{
90+
"stream": {
91+
"name": "activities",
92+
"json_schema": {},
93+
"supported_sync_modes": ["full_refresh", "incremental"],
94+
"source_defined_cursor": true,
95+
"default_cursor_field": ["event_time"],
96+
"namespace": null
97+
},
98+
"sync_mode": "incremental",
99+
"cursor_field": null,
100+
"destination_sync_mode": "append"
88101
}
89102
]
90103
}

airbyte-integrations/connectors/source-facebook-marketing/integration_tests/future_state.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
{
2+
"activities": {
3+
"event_time": "2121-07-25T13:34:26Z",
4+
"include_deleted": true
5+
},
26
"campaigns": {
37
"updated_time": "2121-07-25T13:34:26Z",
48
"include_deleted": true
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"properties": {
3+
"actor_id": {
4+
"type": ["null", "string"]
5+
},
6+
"actor_name": {
7+
"type": ["null", "string"]
8+
},
9+
"application_id": {
10+
"type": ["null", "string"]
11+
},
12+
"application_name": {
13+
"type": ["null", "string"]
14+
},
15+
"date_time_in_timezone": {
16+
"type": ["null", "string"]
17+
},
18+
"event_time": {
19+
"type": "string",
20+
"format": "date-time"
21+
},
22+
"event_type": {
23+
"type": ["null", "string"]
24+
},
25+
"extra_data": {
26+
"type": ["null", "string"]
27+
},
28+
"object_id": {
29+
"type": ["null", "string"]
30+
},
31+
"object_name": {
32+
"type": ["null", "string"]
33+
},
34+
"object_type": {
35+
"type": ["null", "string"]
36+
},
37+
"translated_event_type": {
38+
"type": ["null", "string"]
39+
}
40+
},
41+
"type": ["null", "object"]
42+
}

airbyte-integrations/connectors/source-facebook-marketing/source_facebook_marketing/source.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from source_facebook_marketing.api import API
1313
from source_facebook_marketing.spec import ConnectorConfig
1414
from source_facebook_marketing.streams import (
15+
Activities,
1516
AdAccount,
1617
AdCreatives,
1718
Ads,
@@ -61,7 +62,6 @@ def streams(self, config: Mapping[str, Any]) -> List[Type[Stream]]:
6162
start_date=config.start_date,
6263
end_date=config.end_date,
6364
)
64-
6565
streams = [
6666
AdAccount(api=api),
6767
AdSets(api=api, start_date=config.start_date, end_date=config.end_date, include_deleted=config.include_deleted),
@@ -77,6 +77,7 @@ def streams(self, config: Mapping[str, Any]) -> List[Type[Stream]]:
7777
Campaigns(api=api, start_date=config.start_date, end_date=config.end_date, include_deleted=config.include_deleted),
7878
Images(api=api, start_date=config.start_date, end_date=config.end_date, include_deleted=config.include_deleted),
7979
Videos(api=api, start_date=config.start_date, end_date=config.end_date, include_deleted=config.include_deleted),
80+
Activities(api=api, start_date=config.start_date, end_date=config.end_date, include_deleted=config.include_deleted),
8081
]
8182

8283
return self._update_insights_streams(insights=config.custom_insights, args=insights_args, streams=streams)

airbyte-integrations/connectors/source-facebook-marketing/source_facebook_marketing/streams/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#
44

55
from .streams import (
6+
Activities,
67
AdAccount,
78
AdCreatives,
89
Ads,
@@ -34,4 +35,5 @@
3435
"Campaigns",
3536
"Images",
3637
"Videos",
38+
"Activities",
3739
]

airbyte-integrations/connectors/source-facebook-marketing/source_facebook_marketing/streams/streams.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
import logging
77
from typing import Any, Iterable, List, Mapping, Optional
88

9+
import pendulum
910
import requests
1011
from airbyte_cdk.models import SyncMode
1112
from cached_property import cached_property
13+
from facebook_business.adobjects.abstractobject import AbstractObject
1214
from facebook_business.adobjects.adaccount import AdAccount as FBAdAccount
1315

1416
from .base_insight_streams import AdsInsights
@@ -93,6 +95,45 @@ def list_objects(self, params: Mapping[str, Any]) -> Iterable:
9395
return self._api.account.get_campaigns(params=params)
9496

9597

98+
class Activities(FBMarketingIncrementalStream):
99+
"""doc: https://developers.facebook.com/docs/marketing-api/reference/ad-activity"""
100+
101+
entity_prefix = "activity"
102+
cursor_field = "event_time"
103+
primary_key = None
104+
105+
def list_objects(self, fields: List[str], params: Mapping[str, Any]) -> Iterable:
106+
return self._api.account.get_activities(fields=fields, params=params)
107+
108+
def read_records(
109+
self,
110+
sync_mode: SyncMode,
111+
cursor_field: List[str] = None,
112+
stream_slice: Mapping[str, Any] = None,
113+
stream_state: Mapping[str, Any] = None,
114+
) -> Iterable[Mapping[str, Any]]:
115+
"""Main read method used by CDK"""
116+
loaded_records_iter = self.list_objects(fields=self.fields, params=self.request_params(stream_state=stream_state))
117+
118+
for record in loaded_records_iter:
119+
if isinstance(record, AbstractObject):
120+
yield record.export_all_data() # convert FB object to dict
121+
else:
122+
yield record # execute_in_batch will emmit dicts
123+
124+
def _state_filter(self, stream_state: Mapping[str, Any]) -> Mapping[str, Any]:
125+
"""Additional filters associated with state if any set"""
126+
state_value = stream_state.get(self.cursor_field)
127+
since = self._start_date if not state_value else pendulum.parse(state_value)
128+
129+
potentially_new_records_in_the_past = self._include_deleted and not stream_state.get("include_deleted", False)
130+
if potentially_new_records_in_the_past:
131+
self.logger.info(f"Ignoring bookmark for {self.name} because of enabled `include_deleted` option")
132+
since = self._start_date
133+
134+
return {"since": since.int_timestamp}
135+
136+
96137
class Videos(FBMarketingIncrementalStream):
97138
"""See: https://developers.facebook.com/docs/marketing-api/reference/video"""
98139

airbyte-integrations/connectors/source-facebook-marketing/unit_tests/test_source.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def test_check_connection_exception(self, api, config, logger_mock):
7474
def test_streams(self, config, api):
7575
streams = SourceFacebookMarketing().streams(config)
7676

77-
assert len(streams) == 14
77+
assert len(streams) == 15
7878

7979
def test_spec(self):
8080
spec = SourceFacebookMarketing().spec()

0 commit comments

Comments
 (0)