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

refactor: Update tap to use the LinkedIn 202305 API #68

Merged
merged 37 commits into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
1310030
Update to 202305, remove pivotvalue
NeilGorman104 Jun 9, 2023
b6d57da
Creative, campaign, campaignGroup
lucas5thorneRyanMiranda Jun 11, 2023
54b6aa0
adAnalytics campaign / creative id data
NeilGorman104 Jun 12, 2023
e737a6d
add Campaign group and creative to env template
NeilGorman104 Jun 12, 2023
5f5a953
Change path env variable naming convention
NeilGorman104 Jun 12, 2023
b8be19e
Update client.py
lucas5thorneRyanMiranda Jun 12, 2023
58ddcd5
Update Creative schema column names
NeilGorman104 Jun 12, 2023
da04430
Multiple columns for AdAnalytics
lucas5thorneRyanMiranda Jun 12, 2023
64b2ffb
Url_base functions for campaign/creative/campaingroup, added campaign…
NeilGorman104 Jun 13, 2023
c65bc5a
Remove unused parse statement
NeilGorman104 Jun 13, 2023
baa525e
change campaign_group property name
NeilGorman104 Jun 13, 2023
f1fcd7b
Replace getenv method for AdAnalytics IDs
NeilGorman104 Jun 13, 2023
54efad1
Update README settings / version
NeilGorman104 Jun 13, 2023
863aad8
ci_workflow.yml new configs
NeilGorman104 Jun 13, 2023
44b5363
campaign_group naming fix
NeilGorman104 Jun 13, 2023
9bbadf2
Linting / spelling
NeilGorman104 Jun 13, 2023
f20e3c0
set adanalytics key back to dateRange
NeilGorman104 Jun 13, 2023
aa24858
Merge pull request #1 from NeilGorman104/202305
NeilGorman104 Jun 13, 2023
0fe42bf
[pre-commit.ci] auto fixes
pre-commit-ci[bot] Jun 13, 2023
0b28973
remove loader from meltano.yml
NeilGorman104 Jun 13, 2023
4b37048
Delete target-snowflake--transferwise.lock
NeilGorman104 Jun 13, 2023
97fe955
Merge branch 'main' into main
edgarrmondragon Jun 19, 2023
fe0c811
Updated descriptions in tap.py
lucas5thorneRyanMiranda Jun 22, 2023
0ba054d
Remove api_version property, hardcode 202305 version
NeilGorman104 Jun 23, 2023
4096974
updated bare except
DhrruvRM Jun 27, 2023
0bcb88a
updated get url params
DhrruvRM Jun 27, 2023
ff8b818
[pre-commit.ci] auto fixes
pre-commit-ci[bot] Jun 27, 2023
79cb560
added property value
Jun 28, 2023
5d5b066
Revert "added property value"
NeilGorman104 Jun 28, 2023
bcb4f0b
Updated types
lucas5thorneRyanMiranda Jun 30, 2023
117e661
add tests branch to ci workflow
NeilGorman104 Jun 30, 2023
46ec5a5
Fixed types, url_base, .get
lucas5thorneRyanMiranda Jun 30, 2023
c6da74a
updated get_next_page_token
lucas5thorneRyanMiranda Jun 30, 2023
b9be381
line formatting
NeilGorman104 Jun 30, 2023
f9b8b95
remove test directory in ci_workflow
NeilGorman104 Jun 30, 2023
2116b5b
Merge pull request #2 from NeilGorman104/tests
NeilGorman104 Jul 1, 2023
e28c4bc
Merge branch 'main' into main
edgarrmondragon Jul 5, 2023
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
2 changes: 2 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
TAP_LINKEDIN_ADS_ACCESS_TOKEN=''
TAP_LINKEDIN_ADS_ACCOUNTS=''
TAP_LINKEDIN_ADS_CAMPAIGN=''
TAP_LINKEDIN_ADS_CAMPAIGN_GROUP=''
TAP_LINKEDIN_ADS_CREATIVE=''
TAP_LINKEDIN_ADS_OWNER=''
2 changes: 2 additions & 0 deletions .github/workflows/ci_workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,7 @@ jobs:
TAP_LINKEDIN_ADS_ACCOUNTS: ${{ secrets.accounts }}
TAP_LINKEDIN_ADS_OWNER: ${{ secrets.owner }}
TAP_LINKEDIN_ADS_CAMPAIGN: ${{ secrets.campaign }}
TAP_LINKEDIN_ADS_CAMPAIGN_GROUP: ${{ secrets.campaign_group }}
TAP_LINKEDIN_ADS_CREATIVE: ${{ secrets.creative }}
run: |
poetry run pytest --capture=no
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 16 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,22 @@ Built with the [Meltano Singer SDK](https://sdk.meltano.com).

## Settings

| Setting | Required | Default | Description |
|:--------------------|:--------:|:-------:|:------------|
| access_token | True | None | The token to authenticate against the API service |
| start_date | True | None | The earliest record date to sync |
| end_date | False | 2023-05-09 02:04:18.151589 | The latest record date to sync |
| user_agent | False | tap-linkedin-ads <api_user_email@your_company.com> | API ID |
| api_version | False | 202211 | LinkedInAds API Version |
| accounts | True | None | LinkedInAds Account ID |
| campaign | True | None | LinkedInAds Campaign ID |
| owner | True | None | LinkedInAds Owner ID |
| stream_maps | False | None | Config object for stream maps capability. For more information check out [Stream Maps](https://sdk.meltano.com/en/latest/stream_maps.html). |
| stream_map_config | False | None | User-defined config values to be used within map expressions. |
| flattening_enabled | False | None | 'True' to enable schema flattening and automatically expand nested properties. |
| flattening_max_depth| False | None | The max depth to flatten schemas. |
| Setting | Required | Default | Description |
|:---------------------|:--------:|:--------------------------------------------------:|:--------------------------------------------------------------------------------------------------------------------------------------------|
| access_token | True | None | The token to authenticate against the API service |
| start_date | True | None | The earliest record date to sync |
| end_date | False | 2023-05-09 02:04:18.151589 | The latest record date to sync |
| user_agent | False | tap-linkedin-ads <api_user_email@your_company.com> | API ID |
| api_version | False | 202305 | LinkedInAds API Version |
| accounts | True | None | LinkedInAds Account ID |
| campaign | True | None | LinkedInAds Campaign ID |
| creative | True | None | LinkedInAds Creative ID |
| campaign_group | True | None | LinkedInAds Campaign Group ID |
| owner | True | None | LinkedInAds Owner ID |
| stream_maps | False | None | Config object for stream maps capability. For more information check out [Stream Maps](https://sdk.meltano.com/en/latest/stream_maps.html). |
| stream_map_config | False | None | User-defined config values to be used within map expressions. |
| flattening_enabled | False | None | 'True' to enable schema flattening and automatically expand nested properties. |
| flattening_max_depth | False | None | The max depth to flatten schemas. |

A full list of supported settings and capabilities is available by running: `tap-linkedin-ads --about`

Expand Down
4 changes: 3 additions & 1 deletion meltano.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ plugins:
- name: user_agent
value: 'meltano'
- name: linkedin_version
value: '202211'
value: '202305'
- name: campaign
- name: owner
- name: campaign_group
- name: creative
- name: start_date
value: '2023-01-01T00:00:00Z'
- name: end_date
Expand Down
99 changes: 59 additions & 40 deletions tap_linkedin_ads/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class LinkedInAdsStream(RESTStream):

url_base = "https://api.linkedin.com/rest/"

records_jsonpath = "$.elements[*]" # Or override `parse_response`.
records_jsonpath = "$[*]" # Or override `parse_response`.
next_page_token_jsonpath = (
"$.paging.start" # Or override `get_next_page_token`. # noqa: S105
)
Expand Down Expand Up @@ -63,16 +63,17 @@ def get_next_page_token(
# If pagination is required, return a token which can be used to get the
# next page. If this is the final page, return "None" to end the
# pagination loop.

resp_json = response.json()
if previous_token is None:
previous_token = 0

elements = resp_json.get("elements")

if len(elements) == 0 or len(elements) == previous_token + 1:
return None

try:
elements = resp_json.get("elements")
if len(elements) == 0 or len(elements) == previous_token + 1:
NeilGorman104 marked this conversation as resolved.
Show resolved Hide resolved
return None
except:
page = resp_json
if len(page) == 0 or len(page) == previous_token + 1:
return None
return previous_token + 1

def get_url_params(
Expand Down Expand Up @@ -111,13 +112,14 @@ def parse_response( # noqa: PLR0912
Each record from the source.
"""
resp_json = response.json()

if isinstance(resp_json, list):
results = resp_json
elif resp_json.get("elements") is not None:
if resp_json.get("elements") is not None:
results = resp_json["elements"]
try:
columns = results[0]
except: # noqa: E722
columns = results
pass
try:
created_time = (
columns.get("changeAuditStamps").get("created").get("time")
)
Expand All @@ -132,37 +134,54 @@ def parse_response( # noqa: PLR0912
int(last_modified_time) / 1000,
tz=UTC,
).isoformat()
try:
account_column = columns.get("account")
account_id = int(account_column.split(":")[3])
columns["account_id"] = account_id
except: # noqa: E722, S110
pass
try:
campaign_column = columns.get("campaignGroup")
campaign = int(campaign_column.split(":")[3])
columns["campaign_group_id"] = campaign
except: # noqa: E722, S110
pass
try:
user_column = columns.get("user")
user = user_column.split(":")[3]
columns["user_person_id"] = user
except: # noqa: E722, S110
pass
try:
schedule_column = columns.get("runSchedule").get("start")
columns[
"run_schedule_start"
] = datetime.fromtimestamp( # noqa: DTZ006
int(schedule_column) / 1000,
).isoformat()
except: # noqa: E722, S110
pass
results = [columns]
except: # noqa: E722, S110
pass
else:
results = resp_json
try:
columns = results
created_time = (
columns.get("changeAuditStamps").get("created").get("time")
)
last_modified_time = (
columns.get("changeAuditStamps").get("lastModified").get("time")
)
columns["created_time"] = datetime.fromtimestamp(
int(created_time) / 1000,
tz=UTC,
).isoformat()
columns["last_modified_time"] = datetime.fromtimestamp(
int(last_modified_time) / 1000,
tz=UTC,
).isoformat()
except: # noqa: E722
columns = results
pass

try:
account_column = columns.get("account")
account_id = int(account_column.split(":")[3])
columns["account_id"] = account_id
except: # noqa: E722, S110
pass
try:
campaign_column = columns.get("campaignGroup")
campaign = int(campaign_column.split(":")[3])
columns["campaign_group_id"] = campaign
except: # noqa: E722, S110
pass
try:
schedule_column = columns.get("runSchedule").get("start")
columns["run_schedule_start"] = datetime.fromtimestamp( # noqa: DTZ006
int(schedule_column) / 1000,
).isoformat()
except: # noqa: E722, S110
pass

results = (
resp_json["elements"]
if resp_json.get("elements") is not None
else [columns]
)

yield from results
Loading