Skip to content

Commit

Permalink
🚨🚨 Source LinkedIn Ads: update primary key for Analytics Streams (#36927
Browse files Browse the repository at this point in the history
)

Signed-off-by: Artem Inzhyyants <artem.inzhyyants@gmail.com>
  • Loading branch information
artem1205 committed Apr 15, 2024
1 parent e75ea0f commit da0a7fa
Show file tree
Hide file tree
Showing 11 changed files with 171 additions and 80 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ data:
connectorSubtype: api
connectorType: source
definitionId: 137ece28-5434-455c-8f34-69dc3782f451
dockerImageTag: 0.8.0
dockerImageTag: 1.0.0
dockerRepository: airbyte/source-linkedin-ads
documentationUrl: https://docs.airbyte.com/integrations/sources/linkedin-ads
githubIssueLabel: source-linkedin-ads
Expand All @@ -29,6 +29,25 @@ data:
oss:
enabled: true
releaseStage: generally_available
releases:
breakingChanges:
1.0.0:
message: This upgrade brings changes in primary key to *-analytics streams.
upgradeDeadline: "2024-04-30"
scopedImpact:
- scopeType: stream
impactedScopes:
- "ad_campaign_analytics"
- "ad_creative_analytics"
- "ad_impression_device_analytics"
- "ad_member_company_size_analytics"
- "ad_member_country_analytics"
- "ad_member_job_function_analytics"
- "ad_member_job_title_analytics"
- "ad_member_industry_analytics"
- "ad_member_seniority_analytics"
- "ad_member_region_analytics"
- "ad_member_company_analytics"
suggestedStreams:
streams:
- accounts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ requires = [ "poetry-core>=1.0.0",]
build-backend = "poetry.core.masonry.api"

[tool.poetry]
version = "0.8.0"
version = "1.0.0"
name = "source-linkedin-ads"
description = "Source implementation for Linkedin Ads."
authors = [ "Airbyte <contact@airbyte.io>",]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,15 @@ class LinkedInAdsAnalyticsStream(IncrementalLinkedinAdsStream, ABC):

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

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

def __init__(self, name: str = None, pivot_by: str = None, time_granularity: str = None, **kwargs):
self.user_stream_name = name
Expand Down Expand Up @@ -286,6 +288,8 @@ def chunk_analytics_fields(
for chunk in chunks:
if "dateRange" not in chunk:
chunk.append("dateRange")
if "pivotValues" not in chunk:
chunk.append("pivotValues")
yield from chunks

def read_records(
Expand All @@ -294,15 +298,15 @@ def read_records(
merged_records = defaultdict(dict)
for field_slice in stream_slice:
for rec in super().read_records(stream_slice=field_slice, **kwargs):
merged_records[rec[self.cursor_field]].update(rec)
merged_records[f"{rec[self.cursor_field]}-{rec['pivotValues']}"].update(rec)
yield from merged_records.values()

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


class AdCampaignAnalytics(LinkedInAdsAnalyticsStream):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,6 @@
"otherEngagements": {
"type": ["null", "number"]
},
"pivotValue": {
"type": ["null", "string"]
},
"pivotValues": {
"type": ["null", "array"],
"items": {
Expand Down Expand Up @@ -299,6 +296,9 @@
},
"viralVideoViews": {
"type": ["null", "number"]
},
"pivot": {
"type": ["null", "string"]
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,117 +10,141 @@
"start.month": 1,
"start.year": 2021
},
"fields": "actionClicks,adUnitClicks,approximateUniqueImpressions,cardClicks,cardImpressions,clicks,commentLikes,comments,companyPageClicks,conversionValueInLocalCurrency,costInLocalCurrency,costInUsd,dateRange,documentCompletions,documentFirstQuartileCompletions,documentMidpointCompletions,documentThirdQuartileCompletions,downloadClicks,externalWebsiteConversions"
"fields": "actionClicks,adUnitClicks,approximateUniqueImpressions,cardClicks,cardImpressions,clicks,commentLikes,comments,companyPageClicks,conversionValueInLocalCurrency,costInLocalCurrency,costInUsd,dateRange,documentCompletions,documentFirstQuartileCompletions,documentMidpointCompletions,documentThirdQuartileCompletions,downloadClicks,pivotValues"
},
{
"campaign_id": 123,
"fields": "externalWebsiteConversions,externalWebsitePostClickConversions,externalWebsitePostViewConversions,follows,fullScreenPlays,impressions,jobApplications,jobApplyClicks,landingPageClicks,leadGenerationMailContactInfoShares,leadGenerationMailInterestedClicks,likes,oneClickLeadFormOpens,oneClickLeads,opens,otherEngagements,pivotValues,postClickJobApplications,dateRange",
"dateRange": {
"end.day": 31,
"end.month": 1,
"end.year": 2021,
"start.day": 1,
"start.month": 1,
"start.year": 2021
},
"fields": "externalWebsitePostClickConversions,externalWebsitePostViewConversions,follows,fullScreenPlays,impressions,jobApplications,jobApplyClicks,landingPageClicks,leadGenerationMailContactInfoShares,leadGenerationMailInterestedClicks,likes,oneClickLeadFormOpens,oneClickLeads,opens,otherEngagements,pivotValues,postClickJobApplications,postClickJobApplyClicks,postClickRegistrations,dateRange"
"start.year": 2021,
"end.day": 31,
"end.month": 1,
"end.year": 2021
}
},
{
"campaign_id": 123,
"fields": "postClickJobApplyClicks,postClickRegistrations,postViewJobApplications,postViewJobApplyClicks,postViewRegistrations,reactions,registrations,sends,shares,talentLeads,textUrlClicks,totalEngagements,validWorkEmailLeads,videoCompletions,videoFirstQuartileCompletions,videoMidpointCompletions,videoStarts,videoThirdQuartileCompletions,dateRange,pivotValues",
"dateRange": {
"end.day": 31,
"end.month": 1,
"end.year": 2021,
"start.day": 1,
"start.month": 1,
"start.year": 2021
},
"fields": "postViewJobApplications,postViewJobApplyClicks,postViewRegistrations,reactions,registrations,sends,shares,talentLeads,textUrlClicks,totalEngagements,validWorkEmailLeads,videoCompletions,videoFirstQuartileCompletions,videoMidpointCompletions,videoStarts,videoThirdQuartileCompletions,videoViews,viralCardClicks,viralCardImpressions,dateRange"
"start.year": 2021,
"end.day": 31,
"end.month": 1,
"end.year": 2021
}
},
{
"campaign_id": 123,
"fields": "videoViews,viralCardClicks,viralCardImpressions,viralClicks,viralCommentLikes,viralComments,viralCompanyPageClicks,viralDocumentCompletions,viralDocumentFirstQuartileCompletions,viralDocumentMidpointCompletions,viralDocumentThirdQuartileCompletions,viralDownloadClicks,viralExternalWebsiteConversions,viralExternalWebsitePostClickConversions,viralExternalWebsitePostViewConversions,viralFollows,viralFullScreenPlays,viralImpressions,dateRange,pivotValues",
"dateRange": {
"end.day": 31,
"end.month": 1,
"end.year": 2021,
"start.day": 1,
"start.month": 1,
"start.year": 2021
},
"fields": "viralClicks,viralCommentLikes,viralComments,viralCompanyPageClicks,viralDocumentCompletions,viralDocumentFirstQuartileCompletions,viralDocumentMidpointCompletions,viralDocumentThirdQuartileCompletions,viralDownloadClicks,viralExternalWebsiteConversions,viralExternalWebsitePostClickConversions,viralExternalWebsitePostViewConversions,viralFollows,viralFullScreenPlays,viralImpressions,viralJobApplications,viralJobApplyClicks,viralLandingPageClicks,viralLikes,dateRange"
"start.year": 2021,
"end.day": 31,
"end.month": 1,
"end.year": 2021
}
},
{
"campaign_id": 123,
"fields": "viralJobApplications,viralJobApplyClicks,viralLandingPageClicks,viralLikes,viralOneClickLeadFormOpens,viralOneClickLeads,viralOtherEngagements,viralPostClickJobApplications,viralPostClickJobApplyClicks,viralPostClickRegistrations,viralPostViewJobApplications,viralPostViewJobApplyClicks,viralPostViewRegistrations,viralReactions,viralRegistrations,viralShares,viralTotalEngagements,viralVideoCompletions,dateRange,pivotValues",
"dateRange": {
"start.day": 1,
"start.month": 1,
"start.year": 2021,
"end.day": 31,
"end.month": 1,
"end.year": 2021,
"end.year": 2021
}
},
{
"campaign_id": 123,
"fields": "viralVideoFirstQuartileCompletions,viralVideoMidpointCompletions,viralVideoStarts,viralVideoThirdQuartileCompletions,viralVideoViews,dateRange,pivotValues",
"dateRange": {
"start.day": 1,
"start.month": 1,
"start.year": 2021
},
"fields": "viralOneClickLeadFormOpens,viralOneClickLeads,viralOtherEngagements,viralPostClickJobApplications,viralPostClickJobApplyClicks,viralPostClickRegistrations,viralPostViewJobApplications,viralPostViewJobApplyClicks,viralPostViewRegistrations,viralReactions,viralRegistrations,viralShares,viralTotalEngagements,viralVideoCompletions,viralVideoFirstQuartileCompletions,viralVideoMidpointCompletions,viralVideoStarts,viralVideoThirdQuartileCompletions,viralVideoViews,dateRange"
"start.year": 2021,
"end.day": 31,
"end.month": 1,
"end.year": 2021
}
}
],
[
{
"campaign_id": 123,
"fields": "actionClicks,adUnitClicks,approximateUniqueImpressions,cardClicks,cardImpressions,clicks,commentLikes,comments,companyPageClicks,conversionValueInLocalCurrency,costInLocalCurrency,costInUsd,dateRange,documentCompletions,documentFirstQuartileCompletions,documentMidpointCompletions,documentThirdQuartileCompletions,downloadClicks,pivotValues",
"dateRange": {
"end.day": 2,
"end.month": 3,
"end.year": 2021,
"start.day": 31,
"start.month": 1,
"start.year": 2021
},
"fields": "actionClicks,adUnitClicks,approximateUniqueImpressions,cardClicks,cardImpressions,clicks,commentLikes,comments,companyPageClicks,conversionValueInLocalCurrency,costInLocalCurrency,costInUsd,dateRange,documentCompletions,documentFirstQuartileCompletions,documentMidpointCompletions,documentThirdQuartileCompletions,downloadClicks,externalWebsiteConversions"
"start.year": 2021,
"end.day": 2,
"end.month": 3,
"end.year": 2021
}
},
{
"campaign_id": 123,
"fields": "externalWebsiteConversions,externalWebsitePostClickConversions,externalWebsitePostViewConversions,follows,fullScreenPlays,impressions,jobApplications,jobApplyClicks,landingPageClicks,leadGenerationMailContactInfoShares,leadGenerationMailInterestedClicks,likes,oneClickLeadFormOpens,oneClickLeads,opens,otherEngagements,pivotValues,postClickJobApplications,dateRange",
"dateRange": {
"end.day": 2,
"end.month": 3,
"end.year": 2021,
"start.day": 31,
"start.month": 1,
"start.year": 2021
},
"fields": "externalWebsitePostClickConversions,externalWebsitePostViewConversions,follows,fullScreenPlays,impressions,jobApplications,jobApplyClicks,landingPageClicks,leadGenerationMailContactInfoShares,leadGenerationMailInterestedClicks,likes,oneClickLeadFormOpens,oneClickLeads,opens,otherEngagements,pivotValues,postClickJobApplications,postClickJobApplyClicks,postClickRegistrations,dateRange"
"start.year": 2021,
"end.day": 2,
"end.month": 3,
"end.year": 2021
}
},
{
"campaign_id": 123,
"fields": "postClickJobApplyClicks,postClickRegistrations,postViewJobApplications,postViewJobApplyClicks,postViewRegistrations,reactions,registrations,sends,shares,talentLeads,textUrlClicks,totalEngagements,validWorkEmailLeads,videoCompletions,videoFirstQuartileCompletions,videoMidpointCompletions,videoStarts,videoThirdQuartileCompletions,dateRange,pivotValues",
"dateRange": {
"end.day": 2,
"end.month": 3,
"end.year": 2021,
"start.day": 31,
"start.month": 1,
"start.year": 2021
},
"fields": "postViewJobApplications,postViewJobApplyClicks,postViewRegistrations,reactions,registrations,sends,shares,talentLeads,textUrlClicks,totalEngagements,validWorkEmailLeads,videoCompletions,videoFirstQuartileCompletions,videoMidpointCompletions,videoStarts,videoThirdQuartileCompletions,videoViews,viralCardClicks,viralCardImpressions,dateRange"
"start.year": 2021,
"end.day": 2,
"end.month": 3,
"end.year": 2021
}
},
{
"campaign_id": 123,
"fields": "videoViews,viralCardClicks,viralCardImpressions,viralClicks,viralCommentLikes,viralComments,viralCompanyPageClicks,viralDocumentCompletions,viralDocumentFirstQuartileCompletions,viralDocumentMidpointCompletions,viralDocumentThirdQuartileCompletions,viralDownloadClicks,viralExternalWebsiteConversions,viralExternalWebsitePostClickConversions,viralExternalWebsitePostViewConversions,viralFollows,viralFullScreenPlays,viralImpressions,dateRange,pivotValues",
"dateRange": {
"end.day": 2,
"end.month": 3,
"end.year": 2021,
"start.day": 31,
"start.month": 1,
"start.year": 2021
},
"fields": "viralClicks,viralCommentLikes,viralComments,viralCompanyPageClicks,viralDocumentCompletions,viralDocumentFirstQuartileCompletions,viralDocumentMidpointCompletions,viralDocumentThirdQuartileCompletions,viralDownloadClicks,viralExternalWebsiteConversions,viralExternalWebsitePostClickConversions,viralExternalWebsitePostViewConversions,viralFollows,viralFullScreenPlays,viralImpressions,viralJobApplications,viralJobApplyClicks,viralLandingPageClicks,viralLikes,dateRange"
"start.year": 2021,
"end.day": 2,
"end.month": 3,
"end.year": 2021
}
},
{
"campaign_id": 123,
"fields": "viralJobApplications,viralJobApplyClicks,viralLandingPageClicks,viralLikes,viralOneClickLeadFormOpens,viralOneClickLeads,viralOtherEngagements,viralPostClickJobApplications,viralPostClickJobApplyClicks,viralPostClickRegistrations,viralPostViewJobApplications,viralPostViewJobApplyClicks,viralPostViewRegistrations,viralReactions,viralRegistrations,viralShares,viralTotalEngagements,viralVideoCompletions,dateRange,pivotValues",
"dateRange": {
"start.day": 31,
"start.month": 1,
"start.year": 2021,
"end.day": 2,
"end.month": 3,
"end.year": 2021,
"end.year": 2021
}
},
{
"campaign_id": 123,
"fields": "viralVideoFirstQuartileCompletions,viralVideoMidpointCompletions,viralVideoStarts,viralVideoThirdQuartileCompletions,viralVideoViews,dateRange,pivotValues",
"dateRange": {
"start.day": 31,
"start.month": 1,
"start.year": 2021
},
"fields": "viralOneClickLeadFormOpens,viralOneClickLeads,viralOtherEngagements,viralPostClickJobApplications,viralPostClickJobApplyClicks,viralPostClickRegistrations,viralPostViewJobApplications,viralPostViewJobApplyClicks,viralPostViewRegistrations,viralReactions,viralRegistrations,viralShares,viralTotalEngagements,viralVideoCompletions,viralVideoFirstQuartileCompletions,viralVideoMidpointCompletions,viralVideoStarts,viralVideoThirdQuartileCompletions,viralVideoViews,dateRange"
"start.year": 2021,
"end.day": 2,
"end.month": 3,
"end.year": 2021
}
}
]
]
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"year": 2023
}
},
"pivotValues": ["urn:li:sponsoredCreative:1"],
"commentLikes": 0,
"adUnitClicks": 0,
"companyPageClicks": 0,
Expand Down Expand Up @@ -53,6 +54,7 @@
"year": 2023
}
},
"pivotValues": ["urn:li:sponsoredCreative:1"],
"commentLikes": 0,
"adUnitClicks": 0,
"companyPageClicks": 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"year": 2023
}
},
"pivotValues": ["urn:li:sponsoredCreative:1"],
"viralCardImpressions": 0,
"videoFirstQuartileCompletions": 0,
"textUrlClicks": 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,10 @@ def test_chunk_analytics_fields():
with "dateRange" field presented in each chunk.
"""
expected_output = [
["field_1", "base_field_1", "field_2", "dateRange"],
["base_field_2", "field_3", "field_4", "dateRange"],
["field_5", "field_6", "field_7", "dateRange"],
["field_8", "dateRange"],
["field_1", "base_field_1", "field_2", "dateRange", "pivotValues"],
["base_field_2", "field_3", "field_4", "dateRange", "pivotValues"],
["field_5", "field_6", "field_7", "dateRange", "pivotValues"],
["field_8", "dateRange", "pivotValues"],
]

assert list(LinkedInAdsAnalyticsStream.chunk_analytics_fields(TEST_ANALYTICS_FIELDS, TEST_FIELDS_CHUNK_SIZE)) == expected_output
Expand Down
40 changes: 40 additions & 0 deletions docs/integrations/sources/linkedin-ads-migrations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# LinkedIn Ads Migration Guide

## Upgrading to 1.0.0

Version 1.0.0 introduces changes in primary key for all *-analytics streams (including custom ones).
- "ad_campaign_analytics"
- "ad_creative_analytics"
- "ad_impression_device_analytics"
- "ad_member_company_size_analytics"
- "ad_member_country_analytics"
- "ad_member_job_function_analytics"
- "ad_member_job_title_analytics"
- "ad_member_industry_analytics"
- "ad_member_seniority_analytics"
- "ad_member_region_analytics"
- "ad_member_company_analytics"

## Migration Steps

### Refresh affected schemas and reset data

1. Select **Connections** in the main nav bar.
1. Select the connection(s) affected by the update.
2. Select the **Replication** tab.
1. Select **Refresh source schema**.
2. Select **OK**.
:::note
Any detected schema changes will be listed for your review.
:::
3. Select **Save changes** at the bottom of the page.
1. Ensure the **Reset affected streams** option is checked.
:::note
Depending on destination type you may not be prompted to reset your data.
:::
4. Select **Save connection**.
:::note
This will reset the data in your destination and initiate a fresh sync.
:::

For more information on resetting your data in Airbyte, see [this page](https://docs.airbyte.com/operator-guides/reset).
Loading

0 comments on commit da0a7fa

Please sign in to comment.