Skip to content

Commit da0a7fa

Browse files
authored
🚨🚨 Source LinkedIn Ads: update primary key for Analytics Streams (#36927)
Signed-off-by: Artem Inzhyyants <[email protected]>
1 parent e75ea0f commit da0a7fa

File tree

11 files changed

+171
-80
lines changed

11 files changed

+171
-80
lines changed

‎airbyte-integrations/connectors/source-linkedin-ads/integration_tests/expected_records.jsonl

+14-14
Large diffs are not rendered by default.

‎airbyte-integrations/connectors/source-linkedin-ads/metadata.yaml

+20-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ data:
1111
connectorSubtype: api
1212
connectorType: source
1313
definitionId: 137ece28-5434-455c-8f34-69dc3782f451
14-
dockerImageTag: 0.8.0
14+
dockerImageTag: 1.0.0
1515
dockerRepository: airbyte/source-linkedin-ads
1616
documentationUrl: https://docs.airbyte.com/integrations/sources/linkedin-ads
1717
githubIssueLabel: source-linkedin-ads
@@ -29,6 +29,25 @@ data:
2929
oss:
3030
enabled: true
3131
releaseStage: generally_available
32+
releases:
33+
breakingChanges:
34+
1.0.0:
35+
message: This upgrade brings changes in primary key to *-analytics streams.
36+
upgradeDeadline: "2024-04-30"
37+
scopedImpact:
38+
- scopeType: stream
39+
impactedScopes:
40+
- "ad_campaign_analytics"
41+
- "ad_creative_analytics"
42+
- "ad_impression_device_analytics"
43+
- "ad_member_company_size_analytics"
44+
- "ad_member_country_analytics"
45+
- "ad_member_job_function_analytics"
46+
- "ad_member_job_title_analytics"
47+
- "ad_member_industry_analytics"
48+
- "ad_member_seniority_analytics"
49+
- "ad_member_region_analytics"
50+
- "ad_member_company_analytics"
3251
suggestedStreams:
3352
streams:
3453
- accounts

‎airbyte-integrations/connectors/source-linkedin-ads/pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ requires = [ "poetry-core>=1.0.0",]
33
build-backend = "poetry.core.masonry.api"
44

55
[tool.poetry]
6-
version = "0.8.0"
6+
version = "1.0.0"
77
name = "source-linkedin-ads"
88
description = "Source implementation for Linkedin Ads."
99
authors = [ "Airbyte <[email protected]>",]

‎airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/analytics_streams.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,15 @@ class LinkedInAdsAnalyticsStream(IncrementalLinkedinAdsStream, ABC):
127127

128128
endpoint = "adAnalytics"
129129
# For Analytics streams, the primary_key is the entity of the pivot [Campaign URN, Creative URN, etc.] + `end_date`
130-
primary_key = ["pivotValue", "end_date"]
130+
primary_key = ["pivotValues", "end_date"]
131131
cursor_field = "end_date"
132132
records_limit = 15000
133-
FIELDS_CHUNK_SIZE = 19
133+
FIELDS_CHUNK_SIZE = 18
134134

135135
def get_json_schema(self) -> Mapping[str, Any]:
136-
return ResourceSchemaLoader(package_name_from_class(self.__class__)).get_schema("ad_analytics")
136+
schema = ResourceSchemaLoader(package_name_from_class(self.__class__)).get_schema("ad_analytics")
137+
schema["properties"].update({self.search_param_value: {"type": ["null", "string"]}})
138+
return schema
137139

138140
def __init__(self, name: str = None, pivot_by: str = None, time_granularity: str = None, **kwargs):
139141
self.user_stream_name = name
@@ -286,6 +288,8 @@ def chunk_analytics_fields(
286288
for chunk in chunks:
287289
if "dateRange" not in chunk:
288290
chunk.append("dateRange")
291+
if "pivotValues" not in chunk:
292+
chunk.append("pivotValues")
289293
yield from chunks
290294

291295
def read_records(
@@ -294,15 +298,15 @@ def read_records(
294298
merged_records = defaultdict(dict)
295299
for field_slice in stream_slice:
296300
for rec in super().read_records(stream_slice=field_slice, **kwargs):
297-
merged_records[rec[self.cursor_field]].update(rec)
301+
merged_records[f"{rec[self.cursor_field]}-{rec['pivotValues']}"].update(rec)
298302
yield from merged_records.values()
299303

300304
def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]:
301305
"""
302306
We need to get out the nested complex data structures for further normalization, so the transform_data method is applied.
303307
"""
304308
for rec in transform_data(response.json().get("elements")):
305-
yield rec | {"pivotValue": f"urn:li:{self.search_param_value}:{self.get_primary_key_from_slice(kwargs.get('stream_slice'))}"}
309+
yield rec | {self.search_param_value: self.get_primary_key_from_slice(kwargs.get("stream_slice")), "pivot": self.pivot_by}
306310

307311

308312
class AdCampaignAnalytics(LinkedInAdsAnalyticsStream):

‎airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/schemas/ad_analytics.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,6 @@
107107
"otherEngagements": {
108108
"type": ["null", "number"]
109109
},
110-
"pivotValue": {
111-
"type": ["null", "string"]
112-
},
113110
"pivotValues": {
114111
"type": ["null", "array"],
115112
"items": {
@@ -299,6 +296,9 @@
299296
},
300297
"viralVideoViews": {
301298
"type": ["null", "number"]
299+
},
300+
"pivot": {
301+
"type": ["null", "string"]
302302
}
303303
}
304304
}

‎airbyte-integrations/connectors/source-linkedin-ads/unit_tests/output_slices.json

+75-51
Original file line numberDiff line numberDiff line change
@@ -10,117 +10,141 @@
1010
"start.month": 1,
1111
"start.year": 2021
1212
},
13-
"fields": "actionClicks,adUnitClicks,approximateUniqueImpressions,cardClicks,cardImpressions,clicks,commentLikes,comments,companyPageClicks,conversionValueInLocalCurrency,costInLocalCurrency,costInUsd,dateRange,documentCompletions,documentFirstQuartileCompletions,documentMidpointCompletions,documentThirdQuartileCompletions,downloadClicks,externalWebsiteConversions"
13+
"fields": "actionClicks,adUnitClicks,approximateUniqueImpressions,cardClicks,cardImpressions,clicks,commentLikes,comments,companyPageClicks,conversionValueInLocalCurrency,costInLocalCurrency,costInUsd,dateRange,documentCompletions,documentFirstQuartileCompletions,documentMidpointCompletions,documentThirdQuartileCompletions,downloadClicks,pivotValues"
1414
},
1515
{
1616
"campaign_id": 123,
17+
"fields": "externalWebsiteConversions,externalWebsitePostClickConversions,externalWebsitePostViewConversions,follows,fullScreenPlays,impressions,jobApplications,jobApplyClicks,landingPageClicks,leadGenerationMailContactInfoShares,leadGenerationMailInterestedClicks,likes,oneClickLeadFormOpens,oneClickLeads,opens,otherEngagements,pivotValues,postClickJobApplications,dateRange",
1718
"dateRange": {
18-
"end.day": 31,
19-
"end.month": 1,
20-
"end.year": 2021,
2119
"start.day": 1,
2220
"start.month": 1,
23-
"start.year": 2021
24-
},
25-
"fields": "externalWebsitePostClickConversions,externalWebsitePostViewConversions,follows,fullScreenPlays,impressions,jobApplications,jobApplyClicks,landingPageClicks,leadGenerationMailContactInfoShares,leadGenerationMailInterestedClicks,likes,oneClickLeadFormOpens,oneClickLeads,opens,otherEngagements,pivotValues,postClickJobApplications,postClickJobApplyClicks,postClickRegistrations,dateRange"
21+
"start.year": 2021,
22+
"end.day": 31,
23+
"end.month": 1,
24+
"end.year": 2021
25+
}
2626
},
2727
{
2828
"campaign_id": 123,
29+
"fields": "postClickJobApplyClicks,postClickRegistrations,postViewJobApplications,postViewJobApplyClicks,postViewRegistrations,reactions,registrations,sends,shares,talentLeads,textUrlClicks,totalEngagements,validWorkEmailLeads,videoCompletions,videoFirstQuartileCompletions,videoMidpointCompletions,videoStarts,videoThirdQuartileCompletions,dateRange,pivotValues",
2930
"dateRange": {
30-
"end.day": 31,
31-
"end.month": 1,
32-
"end.year": 2021,
3331
"start.day": 1,
3432
"start.month": 1,
35-
"start.year": 2021
36-
},
37-
"fields": "postViewJobApplications,postViewJobApplyClicks,postViewRegistrations,reactions,registrations,sends,shares,talentLeads,textUrlClicks,totalEngagements,validWorkEmailLeads,videoCompletions,videoFirstQuartileCompletions,videoMidpointCompletions,videoStarts,videoThirdQuartileCompletions,videoViews,viralCardClicks,viralCardImpressions,dateRange"
33+
"start.year": 2021,
34+
"end.day": 31,
35+
"end.month": 1,
36+
"end.year": 2021
37+
}
3838
},
3939
{
4040
"campaign_id": 123,
41+
"fields": "videoViews,viralCardClicks,viralCardImpressions,viralClicks,viralCommentLikes,viralComments,viralCompanyPageClicks,viralDocumentCompletions,viralDocumentFirstQuartileCompletions,viralDocumentMidpointCompletions,viralDocumentThirdQuartileCompletions,viralDownloadClicks,viralExternalWebsiteConversions,viralExternalWebsitePostClickConversions,viralExternalWebsitePostViewConversions,viralFollows,viralFullScreenPlays,viralImpressions,dateRange,pivotValues",
4142
"dateRange": {
42-
"end.day": 31,
43-
"end.month": 1,
44-
"end.year": 2021,
4543
"start.day": 1,
4644
"start.month": 1,
47-
"start.year": 2021
48-
},
49-
"fields": "viralClicks,viralCommentLikes,viralComments,viralCompanyPageClicks,viralDocumentCompletions,viralDocumentFirstQuartileCompletions,viralDocumentMidpointCompletions,viralDocumentThirdQuartileCompletions,viralDownloadClicks,viralExternalWebsiteConversions,viralExternalWebsitePostClickConversions,viralExternalWebsitePostViewConversions,viralFollows,viralFullScreenPlays,viralImpressions,viralJobApplications,viralJobApplyClicks,viralLandingPageClicks,viralLikes,dateRange"
45+
"start.year": 2021,
46+
"end.day": 31,
47+
"end.month": 1,
48+
"end.year": 2021
49+
}
5050
},
5151
{
5252
"campaign_id": 123,
53+
"fields": "viralJobApplications,viralJobApplyClicks,viralLandingPageClicks,viralLikes,viralOneClickLeadFormOpens,viralOneClickLeads,viralOtherEngagements,viralPostClickJobApplications,viralPostClickJobApplyClicks,viralPostClickRegistrations,viralPostViewJobApplications,viralPostViewJobApplyClicks,viralPostViewRegistrations,viralReactions,viralRegistrations,viralShares,viralTotalEngagements,viralVideoCompletions,dateRange,pivotValues",
5354
"dateRange": {
55+
"start.day": 1,
56+
"start.month": 1,
57+
"start.year": 2021,
5458
"end.day": 31,
5559
"end.month": 1,
56-
"end.year": 2021,
60+
"end.year": 2021
61+
}
62+
},
63+
{
64+
"campaign_id": 123,
65+
"fields": "viralVideoFirstQuartileCompletions,viralVideoMidpointCompletions,viralVideoStarts,viralVideoThirdQuartileCompletions,viralVideoViews,dateRange,pivotValues",
66+
"dateRange": {
5767
"start.day": 1,
5868
"start.month": 1,
59-
"start.year": 2021
60-
},
61-
"fields": "viralOneClickLeadFormOpens,viralOneClickLeads,viralOtherEngagements,viralPostClickJobApplications,viralPostClickJobApplyClicks,viralPostClickRegistrations,viralPostViewJobApplications,viralPostViewJobApplyClicks,viralPostViewRegistrations,viralReactions,viralRegistrations,viralShares,viralTotalEngagements,viralVideoCompletions,viralVideoFirstQuartileCompletions,viralVideoMidpointCompletions,viralVideoStarts,viralVideoThirdQuartileCompletions,viralVideoViews,dateRange"
69+
"start.year": 2021,
70+
"end.day": 31,
71+
"end.month": 1,
72+
"end.year": 2021
73+
}
6274
}
6375
],
6476
[
6577
{
6678
"campaign_id": 123,
79+
"fields": "actionClicks,adUnitClicks,approximateUniqueImpressions,cardClicks,cardImpressions,clicks,commentLikes,comments,companyPageClicks,conversionValueInLocalCurrency,costInLocalCurrency,costInUsd,dateRange,documentCompletions,documentFirstQuartileCompletions,documentMidpointCompletions,documentThirdQuartileCompletions,downloadClicks,pivotValues",
6780
"dateRange": {
68-
"end.day": 2,
69-
"end.month": 3,
70-
"end.year": 2021,
7181
"start.day": 31,
7282
"start.month": 1,
73-
"start.year": 2021
74-
},
75-
"fields": "actionClicks,adUnitClicks,approximateUniqueImpressions,cardClicks,cardImpressions,clicks,commentLikes,comments,companyPageClicks,conversionValueInLocalCurrency,costInLocalCurrency,costInUsd,dateRange,documentCompletions,documentFirstQuartileCompletions,documentMidpointCompletions,documentThirdQuartileCompletions,downloadClicks,externalWebsiteConversions"
83+
"start.year": 2021,
84+
"end.day": 2,
85+
"end.month": 3,
86+
"end.year": 2021
87+
}
7688
},
7789
{
7890
"campaign_id": 123,
91+
"fields": "externalWebsiteConversions,externalWebsitePostClickConversions,externalWebsitePostViewConversions,follows,fullScreenPlays,impressions,jobApplications,jobApplyClicks,landingPageClicks,leadGenerationMailContactInfoShares,leadGenerationMailInterestedClicks,likes,oneClickLeadFormOpens,oneClickLeads,opens,otherEngagements,pivotValues,postClickJobApplications,dateRange",
7992
"dateRange": {
80-
"end.day": 2,
81-
"end.month": 3,
82-
"end.year": 2021,
8393
"start.day": 31,
8494
"start.month": 1,
85-
"start.year": 2021
86-
},
87-
"fields": "externalWebsitePostClickConversions,externalWebsitePostViewConversions,follows,fullScreenPlays,impressions,jobApplications,jobApplyClicks,landingPageClicks,leadGenerationMailContactInfoShares,leadGenerationMailInterestedClicks,likes,oneClickLeadFormOpens,oneClickLeads,opens,otherEngagements,pivotValues,postClickJobApplications,postClickJobApplyClicks,postClickRegistrations,dateRange"
95+
"start.year": 2021,
96+
"end.day": 2,
97+
"end.month": 3,
98+
"end.year": 2021
99+
}
88100
},
89101
{
90102
"campaign_id": 123,
103+
"fields": "postClickJobApplyClicks,postClickRegistrations,postViewJobApplications,postViewJobApplyClicks,postViewRegistrations,reactions,registrations,sends,shares,talentLeads,textUrlClicks,totalEngagements,validWorkEmailLeads,videoCompletions,videoFirstQuartileCompletions,videoMidpointCompletions,videoStarts,videoThirdQuartileCompletions,dateRange,pivotValues",
91104
"dateRange": {
92-
"end.day": 2,
93-
"end.month": 3,
94-
"end.year": 2021,
95105
"start.day": 31,
96106
"start.month": 1,
97-
"start.year": 2021
98-
},
99-
"fields": "postViewJobApplications,postViewJobApplyClicks,postViewRegistrations,reactions,registrations,sends,shares,talentLeads,textUrlClicks,totalEngagements,validWorkEmailLeads,videoCompletions,videoFirstQuartileCompletions,videoMidpointCompletions,videoStarts,videoThirdQuartileCompletions,videoViews,viralCardClicks,viralCardImpressions,dateRange"
107+
"start.year": 2021,
108+
"end.day": 2,
109+
"end.month": 3,
110+
"end.year": 2021
111+
}
100112
},
101113
{
102114
"campaign_id": 123,
115+
"fields": "videoViews,viralCardClicks,viralCardImpressions,viralClicks,viralCommentLikes,viralComments,viralCompanyPageClicks,viralDocumentCompletions,viralDocumentFirstQuartileCompletions,viralDocumentMidpointCompletions,viralDocumentThirdQuartileCompletions,viralDownloadClicks,viralExternalWebsiteConversions,viralExternalWebsitePostClickConversions,viralExternalWebsitePostViewConversions,viralFollows,viralFullScreenPlays,viralImpressions,dateRange,pivotValues",
103116
"dateRange": {
104-
"end.day": 2,
105-
"end.month": 3,
106-
"end.year": 2021,
107117
"start.day": 31,
108118
"start.month": 1,
109-
"start.year": 2021
110-
},
111-
"fields": "viralClicks,viralCommentLikes,viralComments,viralCompanyPageClicks,viralDocumentCompletions,viralDocumentFirstQuartileCompletions,viralDocumentMidpointCompletions,viralDocumentThirdQuartileCompletions,viralDownloadClicks,viralExternalWebsiteConversions,viralExternalWebsitePostClickConversions,viralExternalWebsitePostViewConversions,viralFollows,viralFullScreenPlays,viralImpressions,viralJobApplications,viralJobApplyClicks,viralLandingPageClicks,viralLikes,dateRange"
119+
"start.year": 2021,
120+
"end.day": 2,
121+
"end.month": 3,
122+
"end.year": 2021
123+
}
112124
},
113125
{
114126
"campaign_id": 123,
127+
"fields": "viralJobApplications,viralJobApplyClicks,viralLandingPageClicks,viralLikes,viralOneClickLeadFormOpens,viralOneClickLeads,viralOtherEngagements,viralPostClickJobApplications,viralPostClickJobApplyClicks,viralPostClickRegistrations,viralPostViewJobApplications,viralPostViewJobApplyClicks,viralPostViewRegistrations,viralReactions,viralRegistrations,viralShares,viralTotalEngagements,viralVideoCompletions,dateRange,pivotValues",
115128
"dateRange": {
129+
"start.day": 31,
130+
"start.month": 1,
131+
"start.year": 2021,
116132
"end.day": 2,
117133
"end.month": 3,
118-
"end.year": 2021,
134+
"end.year": 2021
135+
}
136+
},
137+
{
138+
"campaign_id": 123,
139+
"fields": "viralVideoFirstQuartileCompletions,viralVideoMidpointCompletions,viralVideoStarts,viralVideoThirdQuartileCompletions,viralVideoViews,dateRange,pivotValues",
140+
"dateRange": {
119141
"start.day": 31,
120142
"start.month": 1,
121-
"start.year": 2021
122-
},
123-
"fields": "viralOneClickLeadFormOpens,viralOneClickLeads,viralOtherEngagements,viralPostClickJobApplications,viralPostClickJobApplyClicks,viralPostClickRegistrations,viralPostViewJobApplications,viralPostViewJobApplyClicks,viralPostViewRegistrations,viralReactions,viralRegistrations,viralShares,viralTotalEngagements,viralVideoCompletions,viralVideoFirstQuartileCompletions,viralVideoMidpointCompletions,viralVideoStarts,viralVideoThirdQuartileCompletions,viralVideoViews,dateRange"
143+
"start.year": 2021,
144+
"end.day": 2,
145+
"end.month": 3,
146+
"end.year": 2021
147+
}
124148
}
125149
]
126150
]

‎airbyte-integrations/connectors/source-linkedin-ads/unit_tests/responses/ad_member_country_analytics/response_1.json

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"year": 2023
2323
}
2424
},
25+
"pivotValues": ["urn:li:sponsoredCreative:1"],
2526
"commentLikes": 0,
2627
"adUnitClicks": 0,
2728
"companyPageClicks": 0,
@@ -53,6 +54,7 @@
5354
"year": 2023
5455
}
5556
},
57+
"pivotValues": ["urn:li:sponsoredCreative:1"],
5658
"commentLikes": 0,
5759
"adUnitClicks": 0,
5860
"companyPageClicks": 0,

‎airbyte-integrations/connectors/source-linkedin-ads/unit_tests/responses/ad_member_country_analytics/response_3.json

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"year": 2023
2020
}
2121
},
22+
"pivotValues": ["urn:li:sponsoredCreative:1"],
2223
"viralCardImpressions": 0,
2324
"videoFirstQuartileCompletions": 0,
2425
"textUrlClicks": 0,

‎airbyte-integrations/connectors/source-linkedin-ads/unit_tests/test_analytics_streams.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,10 @@ def test_chunk_analytics_fields():
8181
with "dateRange" field presented in each chunk.
8282
"""
8383
expected_output = [
84-
["field_1", "base_field_1", "field_2", "dateRange"],
85-
["base_field_2", "field_3", "field_4", "dateRange"],
86-
["field_5", "field_6", "field_7", "dateRange"],
87-
["field_8", "dateRange"],
84+
["field_1", "base_field_1", "field_2", "dateRange", "pivotValues"],
85+
["base_field_2", "field_3", "field_4", "dateRange", "pivotValues"],
86+
["field_5", "field_6", "field_7", "dateRange", "pivotValues"],
87+
["field_8", "dateRange", "pivotValues"],
8888
]
8989

9090
assert list(LinkedInAdsAnalyticsStream.chunk_analytics_fields(TEST_ANALYTICS_FIELDS, TEST_FIELDS_CHUNK_SIZE)) == expected_output
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# LinkedIn Ads Migration Guide
2+
3+
## Upgrading to 1.0.0
4+
5+
Version 1.0.0 introduces changes in primary key for all *-analytics streams (including custom ones).
6+
- "ad_campaign_analytics"
7+
- "ad_creative_analytics"
8+
- "ad_impression_device_analytics"
9+
- "ad_member_company_size_analytics"
10+
- "ad_member_country_analytics"
11+
- "ad_member_job_function_analytics"
12+
- "ad_member_job_title_analytics"
13+
- "ad_member_industry_analytics"
14+
- "ad_member_seniority_analytics"
15+
- "ad_member_region_analytics"
16+
- "ad_member_company_analytics"
17+
18+
## Migration Steps
19+
20+
### Refresh affected schemas and reset data
21+
22+
1. Select **Connections** in the main nav bar.
23+
1. Select the connection(s) affected by the update.
24+
2. Select the **Replication** tab.
25+
1. Select **Refresh source schema**.
26+
2. Select **OK**.
27+
:::note
28+
Any detected schema changes will be listed for your review.
29+
:::
30+
3. Select **Save changes** at the bottom of the page.
31+
1. Ensure the **Reset affected streams** option is checked.
32+
:::note
33+
Depending on destination type you may not be prompted to reset your data.
34+
:::
35+
4. Select **Save connection**.
36+
:::note
37+
This will reset the data in your destination and initiate a fresh sync.
38+
:::
39+
40+
For more information on resetting your data in Airbyte, see [this page](https://docs.airbyte.com/operator-guides/reset).

0 commit comments

Comments
 (0)