@pipedream/linkedin_ads 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/actions/common/report.mjs +62 -0
- package/actions/create-report/create-report.mjs +105 -0
- package/actions/create-report-by-advertiser-account/create-report-by-advertiser-account.mjs +60 -0
- package/actions/create-report-by-campaign/create-report-by-campaign.mjs +69 -0
- package/actions/send-conversion-event/send-conversion-event.mjs +149 -0
- package/common/utils.mjs +54 -0
- package/linkedin_ads.app.mjs +36 -4
- package/package.json +4 -4
- package/sources/new-event-registration-form-response/new-event-registration-form-response.mjs +62 -0
- package/sources/new-lead-gen-form-created/new-lead-gen-form-created.mjs +68 -0
- package/sources/new-lead-gen-form-created/test-event.mjs +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Overview
|
|
2
|
+
|
|
3
|
+
The LinkedIn Ads API on Pipedream enables you to automate and integrate your LinkedIn advertising efforts with other services. Fetch campaign data, manage ad accounts, or automate ad creation and adjustments. With Pipedream, you can trigger workflows with HTTP requests, emails, and on a schedule, and connect to thousands of other apps with minimal effort.
|
|
4
|
+
|
|
5
|
+
# Example Use Cases
|
|
6
|
+
|
|
7
|
+
- **Sync LinkedIn Ad Campaigns to Google Sheets**: Automate the syncing of LinkedIn Ad campaign data into a Google Sheets spreadsheet. This workflow makes it easy to analyze ad performance, share reports with your team, and maintain records without manual data entry.
|
|
8
|
+
|
|
9
|
+
- **LinkedIn Ad Performance Alerts via Slack or Email**: Set up a workflow that monitors your LinkedIn Ad campaigns for key performance metrics, and sends an alert via Slack or Email when your ads meet certain criteria, such as cost per click (CPC) thresholds or impressions goals, ensuring prompt reaction to ad performance.
|
|
10
|
+
|
|
11
|
+
- **Automatic Ad Pausing Based on Budget Constraints**: Create a workflow that checks your LinkedIn Ad spend against your set budget and automatically pauses campaigns if spend exceeds your budget. This helps maintain control over your advertising costs without needing to check in manually.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
import app from "../../linkedin_ads.app.mjs";
|
|
3
|
+
import utils from "../../common/utils.mjs";
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
props: {
|
|
7
|
+
app,
|
|
8
|
+
},
|
|
9
|
+
methods: {
|
|
10
|
+
getDateStr(date) {
|
|
11
|
+
return [
|
|
12
|
+
`day:${date.getDate()}`,
|
|
13
|
+
`month:${date.getMonth() + 1}`,
|
|
14
|
+
`year:${date.getFullYear()}`,
|
|
15
|
+
].join(",");
|
|
16
|
+
},
|
|
17
|
+
getDateRangeParam(startStr, endStr) {
|
|
18
|
+
const { getDateStr } = this;
|
|
19
|
+
const start = getDateStr(new Date(startStr));
|
|
20
|
+
|
|
21
|
+
if (!endStr) {
|
|
22
|
+
return `(start:(${start}))`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const end = getDateStr(new Date(endStr));
|
|
26
|
+
return `(start:(${start}),end:(${end}))`;
|
|
27
|
+
},
|
|
28
|
+
getListParam(list = []) {
|
|
29
|
+
return `List(${list?.map(encodeURIComponent).join(",")})`;
|
|
30
|
+
},
|
|
31
|
+
getListParams(props = {}) {
|
|
32
|
+
return Object.entries(props)
|
|
33
|
+
.reduce((acc, [
|
|
34
|
+
key,
|
|
35
|
+
value,
|
|
36
|
+
]) => {
|
|
37
|
+
if (
|
|
38
|
+
typeof(value) === "function"
|
|
39
|
+
|| value === undefined
|
|
40
|
+
|| value === null
|
|
41
|
+
) {
|
|
42
|
+
return acc;
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
...acc,
|
|
46
|
+
[key]: this.getListParam(
|
|
47
|
+
typeof(value) === "string"
|
|
48
|
+
? JSON.parse(value)
|
|
49
|
+
: value,
|
|
50
|
+
),
|
|
51
|
+
};
|
|
52
|
+
}, {});
|
|
53
|
+
},
|
|
54
|
+
createReport(args = {}) {
|
|
55
|
+
return this.app._makeRequest({
|
|
56
|
+
path: "/adAnalytics",
|
|
57
|
+
paramsSerializer: utils.getParamsSerializer(),
|
|
58
|
+
...args,
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
import common from "../common/report.mjs";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
...common,
|
|
6
|
+
key: "linkedin_ads-create-report",
|
|
7
|
+
name: "Create A Report",
|
|
8
|
+
description: "Queries the Analytics Finder to get analytics for the specified entity i.e company, account, campaign. [See the docs here](https://docs.microsoft.com/en-us/linkedin/marketing/integrations/ads-reporting/ads-reporting#analytics-finder)",
|
|
9
|
+
version: "0.0.4",
|
|
10
|
+
type: "action",
|
|
11
|
+
props: {
|
|
12
|
+
...common.props,
|
|
13
|
+
adAccountId: {
|
|
14
|
+
propDefinition: [
|
|
15
|
+
common.props.app,
|
|
16
|
+
"adAccountId",
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
pivot: {
|
|
20
|
+
propDefinition: [
|
|
21
|
+
common.props.app,
|
|
22
|
+
"pivot",
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
timeGranularity: {
|
|
26
|
+
propDefinition: [
|
|
27
|
+
common.props.app,
|
|
28
|
+
"timeGranularity",
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
dateRangeStart: {
|
|
32
|
+
type: "string",
|
|
33
|
+
label: "Date Range Start",
|
|
34
|
+
description: "Represents the inclusive start time range of the analytics. If unset, it indicates an open range up to the end time. Should be in the [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format, e.g. 2022-12-27.",
|
|
35
|
+
},
|
|
36
|
+
dateRangeEnd: {
|
|
37
|
+
type: "string",
|
|
38
|
+
label: "Date Range End",
|
|
39
|
+
description: "Represents the inclusive end time range of the analytics. Must be after start time if it's present. If unset, it indicates an open range from start time to everything after. Should be in the [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format, e.g. 2022-12-27.",
|
|
40
|
+
optional: true,
|
|
41
|
+
},
|
|
42
|
+
shares: {
|
|
43
|
+
type: "string[]",
|
|
44
|
+
label: "Shares",
|
|
45
|
+
description: "An [Array of Share URN](https://docs.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/share-api). Required unless another facet is provided.",
|
|
46
|
+
optional: true,
|
|
47
|
+
},
|
|
48
|
+
campaigns: {
|
|
49
|
+
type: "string[]",
|
|
50
|
+
label: "Campaigns",
|
|
51
|
+
description: "An [Array of Sponsored Campaign URN](https://docs.microsoft.com/en-us/linkedin/shared/references/v2/ads/adcampaigns?context=linkedin/marketing/context). Required unless another facet is provided.",
|
|
52
|
+
optional: true,
|
|
53
|
+
},
|
|
54
|
+
creatives: {
|
|
55
|
+
type: "string[]",
|
|
56
|
+
label: "Creatives",
|
|
57
|
+
description: "An [Array of Sponsored Creative URN](https://docs.microsoft.com/en-us/linkedin/shared/references/v2/ads/adcreatives?context=linkedin/marketing/context). Required unless another facet is provided.",
|
|
58
|
+
optional: true,
|
|
59
|
+
},
|
|
60
|
+
campaignGroups: {
|
|
61
|
+
type: "string[]",
|
|
62
|
+
label: "Campaign Groups",
|
|
63
|
+
description: "An [Array of Campaign Group URN](https://docs.microsoft.com/en-us/linkedin/shared/references/v2/ads/adcampaigngroups?context=linkedin/marketing/context). Required unless another facet is provided.",
|
|
64
|
+
optional: true,
|
|
65
|
+
},
|
|
66
|
+
accounts: {
|
|
67
|
+
propDefinition: [
|
|
68
|
+
common.props.app,
|
|
69
|
+
"accounts",
|
|
70
|
+
({ adAccountId }) => ({
|
|
71
|
+
adAccountId,
|
|
72
|
+
}),
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
async run({ $ }) {
|
|
77
|
+
const {
|
|
78
|
+
app,
|
|
79
|
+
getDateRangeParam,
|
|
80
|
+
getListParams,
|
|
81
|
+
createReport,
|
|
82
|
+
adAccountId,
|
|
83
|
+
pivot,
|
|
84
|
+
timeGranularity,
|
|
85
|
+
dateRangeStart,
|
|
86
|
+
dateRangeEnd,
|
|
87
|
+
...arrayProps
|
|
88
|
+
} = this;
|
|
89
|
+
|
|
90
|
+
const response = await createReport({
|
|
91
|
+
$,
|
|
92
|
+
params: {
|
|
93
|
+
q: "analytics",
|
|
94
|
+
pivot,
|
|
95
|
+
timeGranularity,
|
|
96
|
+
dateRange: getDateRangeParam(dateRangeStart, dateRangeEnd),
|
|
97
|
+
...getListParams(arrayProps),
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
$.export("$summary", `Successfully retrieved analytics information with ${response.elements.length} elements.`);
|
|
102
|
+
|
|
103
|
+
return response.elements;
|
|
104
|
+
},
|
|
105
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
import common from "../common/report.mjs";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
...common,
|
|
6
|
+
key: "linkedin_ads-create-report-by-advertiser-account",
|
|
7
|
+
name: "Create Report By Advertiser Account",
|
|
8
|
+
description: "Sample query using analytics finder that gets analytics for a particular account for date range starting in a given year. [See the docs here](https://docs.microsoft.com/en-us/linkedin/marketing/integrations/ads-reporting/ads-reporting#sample-request)",
|
|
9
|
+
version: "0.0.4",
|
|
10
|
+
type: "action",
|
|
11
|
+
props: {
|
|
12
|
+
...common.props,
|
|
13
|
+
startYear: {
|
|
14
|
+
propDefinition: [
|
|
15
|
+
common.props.app,
|
|
16
|
+
"startYear",
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
timeGranularity: {
|
|
20
|
+
propDefinition: [
|
|
21
|
+
common.props.app,
|
|
22
|
+
"timeGranularity",
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
adAccountId: {
|
|
26
|
+
propDefinition: [
|
|
27
|
+
common.props.app,
|
|
28
|
+
"adAccountId",
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
async run({ $ }) {
|
|
33
|
+
const {
|
|
34
|
+
app,
|
|
35
|
+
getDateRangeParam,
|
|
36
|
+
startYear,
|
|
37
|
+
timeGranularity,
|
|
38
|
+
adAccountId,
|
|
39
|
+
getListParam,
|
|
40
|
+
createReport,
|
|
41
|
+
} = this;
|
|
42
|
+
|
|
43
|
+
const response = await createReport({
|
|
44
|
+
$,
|
|
45
|
+
params: {
|
|
46
|
+
"q": "analytics",
|
|
47
|
+
"pivot": "ACCOUNT",
|
|
48
|
+
"dateRange": getDateRangeParam(`${startYear}-01-01`),
|
|
49
|
+
timeGranularity,
|
|
50
|
+
"accounts": getListParam([
|
|
51
|
+
app.getSponsoredAccountUrn(adAccountId),
|
|
52
|
+
]),
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
$.export("$summary", "Successfully retrieved analytics");
|
|
57
|
+
|
|
58
|
+
return response;
|
|
59
|
+
},
|
|
60
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
import common from "../common/report.mjs";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
...common,
|
|
6
|
+
key: "linkedin_ads-create-report-by-campaign",
|
|
7
|
+
name: "Query Analytics Finder Campaign Sample",
|
|
8
|
+
description: "Sample query using analytics finder that gets analytics for a particular campaign in a date range starting in a given year. [See the docs here](https://docs.microsoft.com/en-us/linkedin/marketing/integrations/ads-reporting/ads-reporting#analytics-finder)",
|
|
9
|
+
version: "0.0.4",
|
|
10
|
+
type: "action",
|
|
11
|
+
props: {
|
|
12
|
+
...common.props,
|
|
13
|
+
startYear: {
|
|
14
|
+
propDefinition: [
|
|
15
|
+
common.props.app,
|
|
16
|
+
"startYear",
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
timeGranularity: {
|
|
20
|
+
propDefinition: [
|
|
21
|
+
common.props.app,
|
|
22
|
+
"timeGranularity",
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
adAccountId: {
|
|
26
|
+
propDefinition: [
|
|
27
|
+
common.props.app,
|
|
28
|
+
"adAccountId",
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
campaignId: {
|
|
32
|
+
propDefinition: [
|
|
33
|
+
common.props.app,
|
|
34
|
+
"campaignId",
|
|
35
|
+
({ adAccountId }) => ({
|
|
36
|
+
adAccountId,
|
|
37
|
+
}),
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
async run({ $ }) {
|
|
42
|
+
const {
|
|
43
|
+
app,
|
|
44
|
+
getDateRangeParam,
|
|
45
|
+
startYear,
|
|
46
|
+
timeGranularity,
|
|
47
|
+
campaignId,
|
|
48
|
+
getListParam,
|
|
49
|
+
createReport,
|
|
50
|
+
} = this;
|
|
51
|
+
|
|
52
|
+
const response = await createReport({
|
|
53
|
+
$,
|
|
54
|
+
params: {
|
|
55
|
+
"q": "analytics",
|
|
56
|
+
"pivot": "CAMPAIGN",
|
|
57
|
+
"dateRange": getDateRangeParam(`${startYear}-01-01`),
|
|
58
|
+
timeGranularity,
|
|
59
|
+
"campaigns": getListParam([
|
|
60
|
+
app.getSponsoredCampaignUrn(campaignId),
|
|
61
|
+
]),
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
$.export("$summary", "Successfully retrieved analytics");
|
|
66
|
+
|
|
67
|
+
return response;
|
|
68
|
+
},
|
|
69
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { createHash } from "crypto";
|
|
2
|
+
import app from "../../linkedin_ads.app.mjs";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
key: "linkedin_ads-send-conversion-event",
|
|
6
|
+
name: "Send Conversion Event",
|
|
7
|
+
description: "Sends a conversion event to LinkedIn Ads. [See the documentation](https://learn.microsoft.com/en-us/linkedin/marketing/integrations/ads-reporting/conversions-api?view=li-lms-2024-01&tabs=http#streaming-conversion-events)",
|
|
8
|
+
version: "0.0.3",
|
|
9
|
+
type: "action",
|
|
10
|
+
props: {
|
|
11
|
+
app,
|
|
12
|
+
adAccountId: {
|
|
13
|
+
propDefinition: [
|
|
14
|
+
app,
|
|
15
|
+
"adAccountId",
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
conversionId: {
|
|
19
|
+
propDefinition: [
|
|
20
|
+
app,
|
|
21
|
+
"conversionId",
|
|
22
|
+
({ adAccountId }) => ({
|
|
23
|
+
adAccountId,
|
|
24
|
+
}),
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
conversionHappenedAt: {
|
|
28
|
+
type: "string",
|
|
29
|
+
label: "Conversion Happened At",
|
|
30
|
+
description: "Epoch timestamp in milliseconds at which the conversion event happened. Note: If your source records conversion timestamp in seconds, please insert 000 at the end to transform it to milliseconds. Eg. `1590739275000`.",
|
|
31
|
+
},
|
|
32
|
+
email: {
|
|
33
|
+
type: "string",
|
|
34
|
+
label: "Email",
|
|
35
|
+
description: "Email address of the contact associated with the conversion event which needs to be in SHA256 format, a HEX encoded string with a maximum length of 64 characters. Eg. `test@example.com`.",
|
|
36
|
+
},
|
|
37
|
+
firstName: {
|
|
38
|
+
type: "string",
|
|
39
|
+
label: "First Name",
|
|
40
|
+
description: "The first name of the contact to match the conversion.",
|
|
41
|
+
},
|
|
42
|
+
lastName: {
|
|
43
|
+
type: "string",
|
|
44
|
+
label: "Last Name",
|
|
45
|
+
description: "The last name of the contact to match the conversion.",
|
|
46
|
+
},
|
|
47
|
+
conversionValueCurrencyCode: {
|
|
48
|
+
type: "string",
|
|
49
|
+
label: "Conversion Value Currency Code",
|
|
50
|
+
description: "The currency code of the conversion value in ISO format. Eg. `USD`.",
|
|
51
|
+
},
|
|
52
|
+
conversionValueAmount: {
|
|
53
|
+
type: "string",
|
|
54
|
+
label: "Conversion Value Amount",
|
|
55
|
+
description: "The value of the conversion in decimal string. If you set this field, you must also set in the **Conversion Value Currency Code** field. Eg. `100.05`.",
|
|
56
|
+
},
|
|
57
|
+
companyName: {
|
|
58
|
+
type: "string",
|
|
59
|
+
label: "Company Name",
|
|
60
|
+
description: "A plain text string representing the company of the contact to match.",
|
|
61
|
+
optional: true,
|
|
62
|
+
},
|
|
63
|
+
title: {
|
|
64
|
+
type: "string",
|
|
65
|
+
label: "Title",
|
|
66
|
+
description: "A plain text string representing the title name of the contact to match. Eg. `Software Engineer`.",
|
|
67
|
+
optional: true,
|
|
68
|
+
},
|
|
69
|
+
countryCode: {
|
|
70
|
+
type: "string",
|
|
71
|
+
label: "Country Code",
|
|
72
|
+
description: "An ISO standardized [two letter country code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) representing the country of the contact to match. Eg. `US`.",
|
|
73
|
+
optional: true,
|
|
74
|
+
},
|
|
75
|
+
eventId: {
|
|
76
|
+
type: "string",
|
|
77
|
+
label: "Event ID",
|
|
78
|
+
description: "The unique id generated by advertisers to indicate each event. This field is used for [deduplication](https://learn.microsoft.com/en-us/linkedin/marketing/conversions/deduplication?view=li-lms-2024-01).",
|
|
79
|
+
optional: true,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
methods: {
|
|
83
|
+
emailToSha256(email) {
|
|
84
|
+
return createHash("sha256")
|
|
85
|
+
.update(email)
|
|
86
|
+
.digest("hex");
|
|
87
|
+
},
|
|
88
|
+
sendConversionEvent(args = {}) {
|
|
89
|
+
return this.app._makeRequest({
|
|
90
|
+
debug: true,
|
|
91
|
+
method: "POST",
|
|
92
|
+
path: "/conversionEvents",
|
|
93
|
+
...args,
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
async run({ $ }) {
|
|
98
|
+
const {
|
|
99
|
+
app,
|
|
100
|
+
emailToSha256,
|
|
101
|
+
sendConversionEvent,
|
|
102
|
+
conversionId,
|
|
103
|
+
conversionHappenedAt,
|
|
104
|
+
conversionValueCurrencyCode,
|
|
105
|
+
conversionValueAmount,
|
|
106
|
+
email,
|
|
107
|
+
firstName,
|
|
108
|
+
lastName,
|
|
109
|
+
companyName,
|
|
110
|
+
title,
|
|
111
|
+
countryCode,
|
|
112
|
+
eventId,
|
|
113
|
+
} = this;
|
|
114
|
+
|
|
115
|
+
await sendConversionEvent({
|
|
116
|
+
$,
|
|
117
|
+
data: {
|
|
118
|
+
conversion: app.getConversionUrn(conversionId),
|
|
119
|
+
conversionHappenedAt: Number(conversionHappenedAt),
|
|
120
|
+
conversionValue: {
|
|
121
|
+
currencyCode: conversionValueCurrencyCode,
|
|
122
|
+
amount: conversionValueAmount,
|
|
123
|
+
},
|
|
124
|
+
user: {
|
|
125
|
+
userIds: [
|
|
126
|
+
{
|
|
127
|
+
idType: "SHA256_EMAIL",
|
|
128
|
+
idValue: emailToSha256(email),
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
userInfo: {
|
|
132
|
+
firstName,
|
|
133
|
+
lastName,
|
|
134
|
+
companyName,
|
|
135
|
+
title,
|
|
136
|
+
countryCode,
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
eventId,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
$.export("$summary", "Successfully sent conversion event to LinkedIn Ads");
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
success: true,
|
|
147
|
+
};
|
|
148
|
+
},
|
|
149
|
+
};
|
package/common/utils.mjs
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
function encodeParamKeys(keys = []) {
|
|
2
|
+
return (key, value) => {
|
|
3
|
+
if (!keys.includes(key)) {
|
|
4
|
+
return `${key}=${value}`;
|
|
5
|
+
}
|
|
6
|
+
const [
|
|
7
|
+
firstPart,
|
|
8
|
+
...otherParts
|
|
9
|
+
] = value.split(":");
|
|
10
|
+
|
|
11
|
+
if (!firstPart?.startsWith("(")) {
|
|
12
|
+
const encoded = encodeURIComponent(value)
|
|
13
|
+
.replace("(", "%28")
|
|
14
|
+
.replace(")", "%29");
|
|
15
|
+
return `${key}=${encoded}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return `${key}=${firstPart}:${encodeURIComponent(otherParts.join(":"))}`;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function defaultEncodeFn(key, value) {
|
|
23
|
+
return `${key}=${value}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function encodeFn(key, value) {
|
|
27
|
+
return `${key}=${encodeURIComponent(value)}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getParamsSerializer(encodeFn = defaultEncodeFn) {
|
|
31
|
+
return (params) => {
|
|
32
|
+
return Object.entries(params)
|
|
33
|
+
.map(([
|
|
34
|
+
key,
|
|
35
|
+
value,
|
|
36
|
+
]) => encodeFn(key, value))
|
|
37
|
+
.join("&");
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function transformResponse(jsonStr) {
|
|
42
|
+
// Regex to target specifically "id" fields, capturing
|
|
43
|
+
// the number to convert it into a string
|
|
44
|
+
// Because the number can be a long number or big integer
|
|
45
|
+
const response = jsonStr.replace(/("id"\s*:\s*)(\d+)/g, "$1\"$2\"");
|
|
46
|
+
return JSON.parse(response);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default {
|
|
50
|
+
encodeParamKeys,
|
|
51
|
+
encodeFn,
|
|
52
|
+
getParamsSerializer,
|
|
53
|
+
transformResponse,
|
|
54
|
+
};
|
package/linkedin_ads.app.mjs
CHANGED
|
@@ -123,6 +123,31 @@ export default {
|
|
|
123
123
|
}));
|
|
124
124
|
},
|
|
125
125
|
},
|
|
126
|
+
leadFormId: {
|
|
127
|
+
type: "string",
|
|
128
|
+
label: "Lead Form ID",
|
|
129
|
+
description: "Select the lead form to retrieve responses for.",
|
|
130
|
+
async options({
|
|
131
|
+
page, adAccountId,
|
|
132
|
+
}) {
|
|
133
|
+
const count = 20;
|
|
134
|
+
const { elements } = await this.searchLeadForms({
|
|
135
|
+
params: {
|
|
136
|
+
q: "owner",
|
|
137
|
+
owner: `(sponsoredAccount:${this.getSponsoredAccountUrn(adAccountId)})`,
|
|
138
|
+
count,
|
|
139
|
+
start: page * count,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
return elements.map(({
|
|
143
|
+
id,
|
|
144
|
+
name: label,
|
|
145
|
+
}) => ({
|
|
146
|
+
label,
|
|
147
|
+
value: String(id),
|
|
148
|
+
}));
|
|
149
|
+
},
|
|
150
|
+
},
|
|
126
151
|
},
|
|
127
152
|
methods: {
|
|
128
153
|
...app.methods,
|
|
@@ -141,6 +166,9 @@ export default {
|
|
|
141
166
|
getEventUrn(id) {
|
|
142
167
|
return `urn:li:event:${id}`;
|
|
143
168
|
},
|
|
169
|
+
getVersionedLeadGenFormUrn(id, version = 1) {
|
|
170
|
+
return `urn:li:versionedLeadGenForm:(urn:li:leadGenForm:${id},${version})`;
|
|
171
|
+
},
|
|
144
172
|
searchCampaigns({
|
|
145
173
|
adAccountId, ...args
|
|
146
174
|
} = {}) {
|
|
@@ -154,18 +182,24 @@ export default {
|
|
|
154
182
|
"owner",
|
|
155
183
|
];
|
|
156
184
|
return this._makeRequest({
|
|
157
|
-
debug: true,
|
|
158
185
|
path: "/leadForms",
|
|
159
186
|
paramsSerializer: utils.getParamsSerializer(utils.encodeParamKeys(keys)),
|
|
160
187
|
...args,
|
|
161
188
|
});
|
|
162
189
|
},
|
|
190
|
+
getLeadForm({
|
|
191
|
+
leadFormId, ...args
|
|
192
|
+
} = {}) {
|
|
193
|
+
return this._makeRequest({
|
|
194
|
+
path: `/leadForms/${leadFormId}`,
|
|
195
|
+
...args,
|
|
196
|
+
});
|
|
197
|
+
},
|
|
163
198
|
searchEvents(args = {}) {
|
|
164
199
|
const keys = [
|
|
165
200
|
"organizer",
|
|
166
201
|
];
|
|
167
202
|
return this._makeRequest({
|
|
168
|
-
debug: true,
|
|
169
203
|
path: "/events",
|
|
170
204
|
paramsSerializer: utils.getParamsSerializer(utils.encodeParamKeys(keys)),
|
|
171
205
|
transformResponse: utils.transformResponse,
|
|
@@ -174,7 +208,6 @@ export default {
|
|
|
174
208
|
},
|
|
175
209
|
searchConversions(args = {}) {
|
|
176
210
|
return this._makeRequest({
|
|
177
|
-
debug: true,
|
|
178
211
|
path: "/conversions",
|
|
179
212
|
paramsSerializer: utils.getParamsSerializer(utils.encodeFn),
|
|
180
213
|
...args,
|
|
@@ -187,7 +220,6 @@ export default {
|
|
|
187
220
|
"associatedEntity",
|
|
188
221
|
];
|
|
189
222
|
return this._makeRequest({
|
|
190
|
-
debug: true,
|
|
191
223
|
path: "/leadFormResponses",
|
|
192
224
|
paramsSerializer: utils.getParamsSerializer(utils.encodeParamKeys(keys)),
|
|
193
225
|
...args,
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pipedream/linkedin_ads",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Pipedream LinkedIn Ads Components",
|
|
5
5
|
"main": "linkedin_ads.app.mjs",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"pipedream",
|
|
8
8
|
"linkedin_ads"
|
|
9
9
|
],
|
|
10
|
-
"files": [
|
|
11
|
-
"dist"
|
|
12
|
-
],
|
|
13
10
|
"homepage": "https://pipedream.com/apps/linkedin_ads",
|
|
14
11
|
"author": "Pipedream <support@pipedream.com> (https://pipedream.com/)",
|
|
15
12
|
"publishConfig": {
|
|
16
13
|
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@pipedream/platform": "^3.0.3"
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform";
|
|
2
|
+
import app from "../../linkedin_ads.app.mjs";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
key: "linkedin_ads-new-event-registration-form-response",
|
|
6
|
+
name: "New Event Registration Form Response",
|
|
7
|
+
description: "Emit new event when a fresh response is received on the event registration form. User needs to configure the prop of the specific event. [See the documentation](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/organizations/events?view=li-lms-2024-01&tabs=http)",
|
|
8
|
+
version: "0.0.3",
|
|
9
|
+
type: "source",
|
|
10
|
+
dedupe: "unique",
|
|
11
|
+
props: {
|
|
12
|
+
app,
|
|
13
|
+
timer: {
|
|
14
|
+
type: "$.interface.timer",
|
|
15
|
+
default: {
|
|
16
|
+
intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
organizationId: {
|
|
20
|
+
propDefinition: [
|
|
21
|
+
app,
|
|
22
|
+
"organizationId",
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
eventId: {
|
|
26
|
+
propDefinition: [
|
|
27
|
+
app,
|
|
28
|
+
"eventId",
|
|
29
|
+
({ organizationId }) => ({
|
|
30
|
+
organizationId,
|
|
31
|
+
}),
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
async run() {
|
|
36
|
+
const {
|
|
37
|
+
app,
|
|
38
|
+
organizationId,
|
|
39
|
+
eventId,
|
|
40
|
+
} = this;
|
|
41
|
+
|
|
42
|
+
const { elements } = await app.searchLeadFormResponses({
|
|
43
|
+
params: {
|
|
44
|
+
q: "owner",
|
|
45
|
+
owner: `(organization:${app.getOrganizationUrn(organizationId)})`,
|
|
46
|
+
leadType: "(leadType:EVENT)",
|
|
47
|
+
limitedToTestLeads: false,
|
|
48
|
+
associatedEntity: app.getEventUrn(eventId),
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
elements
|
|
53
|
+
.reverse()
|
|
54
|
+
.forEach((event) => {
|
|
55
|
+
this.$emit(event, {
|
|
56
|
+
id: event.id,
|
|
57
|
+
summary: `New Event Registration: ${event.id}`,
|
|
58
|
+
ts: event.submittedAt,
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform";
|
|
2
|
+
import app from "../../linkedin_ads.app.mjs";
|
|
3
|
+
import sampleEmit from "./test-event.mjs";
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
key: "linkedin_ads-new-lead-gen-form-created",
|
|
7
|
+
name: "New Lead Gen Form Created",
|
|
8
|
+
description: "Emit new event when a new lead is captured through a form. [See the documentation](https://learn.microsoft.com/en-us/linkedin/marketing/lead-sync/leadsync?view=li-lms-2023-07&tabs=http#find-lead-form-responses-by-owner)",
|
|
9
|
+
version: "0.0.2",
|
|
10
|
+
type: "source",
|
|
11
|
+
dedupe: "unique",
|
|
12
|
+
props: {
|
|
13
|
+
app,
|
|
14
|
+
timer: {
|
|
15
|
+
type: "$.interface.timer",
|
|
16
|
+
default: {
|
|
17
|
+
intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
adAccountId: {
|
|
21
|
+
propDefinition: [
|
|
22
|
+
app,
|
|
23
|
+
"adAccountId",
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
leadFormId: {
|
|
27
|
+
propDefinition: [
|
|
28
|
+
app,
|
|
29
|
+
"leadFormId",
|
|
30
|
+
({ adAccountId }) => ({
|
|
31
|
+
adAccountId,
|
|
32
|
+
}),
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
async run() {
|
|
37
|
+
const {
|
|
38
|
+
app,
|
|
39
|
+
adAccountId,
|
|
40
|
+
leadFormId,
|
|
41
|
+
} = this;
|
|
42
|
+
|
|
43
|
+
const { versionId: leadFormVersionId } = await app.getLeadForm({
|
|
44
|
+
leadFormId,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const { elements } = await app.searchLeadFormResponses({
|
|
48
|
+
params: {
|
|
49
|
+
q: "owner",
|
|
50
|
+
owner: `(sponsoredAccount:${app.getSponsoredAccountUrn(adAccountId)})`,
|
|
51
|
+
leadType: "(leadType:SPONSORED)",
|
|
52
|
+
limitedToTestLeads: false,
|
|
53
|
+
versionedLeadGenFormUrn: app.getVersionedLeadGenFormUrn(leadFormId, leadFormVersionId),
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
elements
|
|
58
|
+
.reverse()
|
|
59
|
+
.forEach((event) => {
|
|
60
|
+
this.$emit(event, {
|
|
61
|
+
id: event.id,
|
|
62
|
+
summary: `New Lead Gen: ${event.id}`,
|
|
63
|
+
ts: event.submittedAt,
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
sampleEmit,
|
|
68
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
"owner": {
|
|
3
|
+
"sponsoredAccount": "urn:li:sponsoredAccount:1232131"
|
|
4
|
+
},
|
|
5
|
+
"submitter": "urn:li:person:0",
|
|
6
|
+
"leadType": "SPONSORED",
|
|
7
|
+
"leadMetadata": {
|
|
8
|
+
"sponsoredLeadMetadata": {
|
|
9
|
+
"campaign": "urn:li:sponsoredCampaign:32131231"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"versionedLeadGenFormUrn": "urn:li:versionedLeadGenForm:(urn:li:leadGenForm:3213,1)",
|
|
13
|
+
"id": "52dcb737-26f9-4af3-b1fc-935b3e93f838-4",
|
|
14
|
+
"submittedAt": 1706741502694,
|
|
15
|
+
"testLead": true,
|
|
16
|
+
"associatedEntity": {
|
|
17
|
+
"associatedCreative": "urn:li:sponsoredCreative:369799186"
|
|
18
|
+
},
|
|
19
|
+
"formResponse": {
|
|
20
|
+
"consentResponses": [],
|
|
21
|
+
"answers": [
|
|
22
|
+
{
|
|
23
|
+
"answerDetails": {
|
|
24
|
+
"textQuestionAnswer": {
|
|
25
|
+
"answer": "test1@gmail.com"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"questionId": 20502584796
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"answerDetails": {
|
|
32
|
+
"textQuestionAnswer": {
|
|
33
|
+
"answer": "Test 1"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"questionId": 20502643516
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"answerDetails": {
|
|
40
|
+
"textQuestionAnswer": {
|
|
41
|
+
"answer": "Test 1"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"questionId": 20502667260
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
};
|