diff --git a/components/google_ads/actions/add-contact-to-list-by-email/add-contact-to-list-by-email.mjs b/components/google_ads/actions/add-contact-to-list-by-email/add-contact-to-list-by-email.mjs index d8564b54c2101..0e12b1c8f5ee4 100644 --- a/components/google_ads/actions/add-contact-to-list-by-email/add-contact-to-list-by-email.mjs +++ b/components/google_ads/actions/add-contact-to-list-by-email/add-contact-to-list-by-email.mjs @@ -7,7 +7,7 @@ export default { key: "google_ads-add-contact-to-list-by-email", name: "Add Contact to Customer List by Email", description: "Adds a contact to a specific customer list in Google Ads. Lists typically update in 6 to 12 hours after operation. [See the documentation](https://developers.google.com/google-ads/api/docs/remarketing/audience-segments/customer-match/get-started)", - version: "0.1.0", + version: "0.1.1", type: "action", props: { ...common.props, diff --git a/components/google_ads/actions/create-customer-list/create-customer-list.mjs b/components/google_ads/actions/create-customer-list/create-customer-list.mjs index d2c4c3da752fb..989a0e456b566 100644 --- a/components/google_ads/actions/create-customer-list/create-customer-list.mjs +++ b/components/google_ads/actions/create-customer-list/create-customer-list.mjs @@ -16,7 +16,7 @@ export default { name: "Create Customer List", description: "Create a new customer list in Google Ads. [See the documentation](https://developers.google.com/google-ads/api/rest/reference/rest/v16/UserList)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { ...common.props, diff --git a/components/google_ads/actions/create-report/common-constants.mjs b/components/google_ads/actions/create-report/common-constants.mjs new file mode 100644 index 0000000000000..e0b2ee19d7a54 --- /dev/null +++ b/components/google_ads/actions/create-report/common-constants.mjs @@ -0,0 +1,55 @@ +export const DATE_RANGE_OPTIONS = [ + { + value: "CUSTOM", + label: "Specify a custom date range", + }, + { + value: "TODAY", + label: "Today only", + }, + { + value: "YESTERDAY", + label: "Yesterday only", + }, + { + value: "LAST_7_DAYS", + label: "The last 7 days not including today", + }, + { + value: "LAST_BUSINESS_WEEK", + label: + "The 5 day business week, Monday through Friday, of the previous business week", + }, + { + value: "THIS_MONTH", + label: "All days in the current month", + }, + { + value: "LAST_MONTH", + label: "All days in the previous month", + }, + { + value: "LAST_14_DAYS", + label: "The last 14 days not including today", + }, + { + value: "LAST_30_DAYS", + label: "The last 30 days not including today", + }, + { + value: "THIS_WEEK_SUN_TODAY", + label: "The period between the previous Sunday and the current day", + }, + { + value: "THIS_WEEK_MON_TODAY", + label: "The period between the previous Monday and the current day", + }, + { + value: "LAST_WEEK_SUN_SAT", + label: "The 7-day period starting with the previous Sunday", + }, + { + value: "LAST_WEEK_MON_SUN", + label: "The 7-day period starting with the previous Monday", + }, +]; diff --git a/components/google_ads/actions/create-report/create-report.mjs b/components/google_ads/actions/create-report/create-report.mjs index 5807f8ce55437..d0d49abc0d9d7 100644 --- a/components/google_ads/actions/create-report/create-report.mjs +++ b/components/google_ads/actions/create-report/create-report.mjs @@ -4,6 +4,8 @@ import { ad } from "../../common/resources/ad.mjs"; import { campaign } from "../../common/resources/campaign.mjs"; import { customer } from "../../common/resources/customer.mjs"; import { ConfigurationError } from "@pipedream/platform"; +import { DATE_RANGE_OPTIONS } from "./common-constants.mjs"; +import { checkPrefix } from "../../common/utils.mjs"; const RESOURCES = [ adGroup, @@ -17,7 +19,7 @@ export default { key: "google_ads-create-report", name: "Create Report", description: "Generates a report from your Google Ads data. [See the documentation](https://developers.google.com/google-ads/api/fields/v16/overview)", - version: "0.0.2", + version: "0.1.0", type: "action", props: { ...common.props, @@ -43,10 +45,60 @@ export default { alertType: "info", content: `[See the documentation](https://developers.google.com/google-ads/api/fields/v16/${value}) for more information on available fields, segments and metrics.`, }, + objectFilter: { + type: "string[]", + label: `Filter by ${label}s`, + description: `Select the ${label}s to generate a report for (or leave blank for all ${label}s)`, + optional: true, + useQuery: true, + options: async ({ + query, prevContext: { nextPageToken: pageToken }, + }) => { + const { + accountId, customerClientId, resource, + } = this; + const { + results, nextPageToken, + } = await this.googleAds.listResources({ + accountId, + customerClientId, + resource, + query, + pageToken, + }); + const options = results?.map?.((item) => this.getResourceOption(item, resource)); + return { + options, + context: { + nextPageToken, + }, + }; + }, + }, + dateRange: { + type: "string", + label: "Date Range", + description: "Select a date range for the report", + options: DATE_RANGE_OPTIONS, + optional: true, + reloadProps: true, + }, + ...(this.dateRange === "CUSTOM" && { + startDate: { + type: "string", + label: "Start Date", + description: "The start date, in `YYYY-MM-DD` format", + }, + endDate: { + type: "string", + label: "End Date", + description: "The end date, in `YYYY-MM-DD` format", + }, + }), fields: { type: "string[]", label: "Fields", - description: `${label} data fields to obtain`, + description: "Select any fields you want to include in your report.", options: resource.fields, optional: true, reloadProps: true, @@ -54,15 +106,18 @@ export default { segments: { type: "string[]", label: "Segments", - description: `${label} segments to obtain [more info on the documentation](https://developers.google.com/google-ads/api/fields/v16/segments)`, + description: "Select any segments you want to include in your report. See the documentation [here](https://developers.google.com/google-ads/api/fields/v16/segments)", options: resource.segments, + default: [ + "segments.date", + ], optional: true, reloadProps: true, }, metrics: { type: "string[]", label: "Metrics", - description: `${label} metrics to obtain [more info on the documentation](https://developers.google.com/google-ads/api/fields/v16/metrics)`, + description: "Select any metrics you want to include in your report. See the documentation [here](https://developers.google.com/google-ads/api/fields/v16/metrics)", options: resource.metrics, optional: true, reloadProps: true, @@ -114,17 +169,48 @@ export default { }; }, methods: { + getResourceOption(item, resource) { + let label, value; + switch (resource) { + case "campaign": + label = item.campaign.name; + value = item.campaign.id; + break; + + case "customer": + label = item.customer.descriptiveName; + value = item.customer.id; + break; + + case "ad_group": + label = item.adGroup.name; + value = item.adGroup.id; + break; + + case "ad_group_ad": + label = item.adGroupAd.ad.name; + value = item.adGroupAd.ad.id; + break; + } + + return { + label, + value, + }; + }, buildQuery() { const { - resource, limit, orderBy, direction, + resource, fields, segments, metrics, limit, orderBy, direction, objectFilter, dateRange, } = this; - const fields = this.fields?.map((i) => `${resource}.${i}`) ?? []; - const segments = this.segments?.map((i) => `segments.${i}`) ?? []; - const metrics = this.metrics?.map((i) => `metrics.${i}`) ?? []; + + const filteredSegments = dateRange + ? segments + : segments?.filter((i) => i !== "segments.date"); + const selection = [ - ...fields, - ...segments, - ...metrics, + ...checkPrefix(fields, resource), + ...checkPrefix(filteredSegments, "segments"), + ...checkPrefix(metrics, "metrics"), ]; if (!selection.length) { @@ -132,8 +218,22 @@ export default { } let query = `SELECT ${selection.join(", ")} FROM ${resource}`; + if (objectFilter) { + query += ` WHERE ${resource === "ad_group_ad" + ? "ad_group_ad.ad" + : resource}.id IN (${objectFilter.join?.(", ") ?? objectFilter})`; + } + if (dateRange) { + const dateClause = dateRange === "CUSTOM" + ? `BETWEEN '${this.startDate}' AND '${this.endDate}'` + : `DURING ${dateRange}`; + query += ` ${objectFilter + ? "AND" + : "WHERE"} segments.date ${dateClause}`; + } + if (orderBy && direction) { - query += ` ORDER BY ${`${resource}.${orderBy}`} ${direction}`; + query += ` ORDER BY ${orderBy} ${direction}`; } if (limit) { query += ` LIMIT ${limit}`; @@ -155,7 +255,7 @@ export default { const { length } = results; - $.export("$summary", `Sucessfully obtained ${length} result${length === 1 + $.export("$summary", `Successfully obtained ${length} result${length === 1 ? "" : "s"}`); return { diff --git a/components/google_ads/actions/send-offline-conversion/send-offline-conversion.mjs b/components/google_ads/actions/send-offline-conversion/send-offline-conversion.mjs index d13134351432e..9100619b1bf94 100644 --- a/components/google_ads/actions/send-offline-conversion/send-offline-conversion.mjs +++ b/components/google_ads/actions/send-offline-conversion/send-offline-conversion.mjs @@ -8,7 +8,7 @@ export default { key: "google_ads-send-offline-conversion", name: "Send Offline Conversion", description: "Send an event from to Google Ads to track offline conversions. [See the documentation](https://developers.google.com/google-ads/api/rest/reference/rest/v16/ConversionAction)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { ...common.props, diff --git a/components/google_ads/common/queries.mjs b/components/google_ads/common/queries.mjs index 129ccd398e1e2..b89900f624c3e 100644 --- a/components/google_ads/common/queries.mjs +++ b/components/google_ads/common/queries.mjs @@ -73,7 +73,7 @@ function listCampaigns({ const defaultFields = [ "id", "name", - ]; + ].map((s) => `campaign.${s}`); if (typeof fields === "string") { fields = fields.split(",").map((s) => s.trim()); } @@ -91,7 +91,22 @@ function listCampaigns({ ? ` WHERE ${savedIds.map((id) => `campaign.id != ${id}`).join(" AND ")}` : ""; - return `SELECT ${fields.map((s) => `campaign.${s}`).join(", ")} FROM campaign${filter}`; + return `SELECT ${fields.join(", ")} FROM campaign${filter}`; +} + +function listResources(resource, query) { + const name = resource === "customer" + ? "descriptive_name" + : "name"; + const fieldResource = resource === "ad_group_ad" + ? "ad_group_ad.ad" + : resource; + + let result = `SELECT ${fieldResource}.id, ${fieldResource}.${name} FROM ${resource}`; + if (query) { + result += ` WHERE ${fieldResource}.${name} LIKE '%${query}%'`; + } + return result; } export const QUERIES = { @@ -101,5 +116,6 @@ export const QUERIES = { listLeadForms, listLeadFormSubmissionData, listRemarketingActions, + listResources, listUserLists, }; diff --git a/components/google_ads/common/resources/ad.mjs b/components/google_ads/common/resources/ad.mjs index 76b7d69b3de1d..586d9394c716e 100644 --- a/components/google_ads/common/resources/ad.mjs +++ b/components/google_ads/common/resources/ad.mjs @@ -1,3 +1,5 @@ +import { getOption } from "../utils.mjs"; + const fields = [ "action_items", "ad.added_by_google_ads", @@ -213,7 +215,7 @@ const fields = [ "primary_status_reasons", "resource_name", "status", -]; +].map((f) => getOption(f, "ad_group_ad")); const segments = [ "ad_destination_type", @@ -226,20 +228,14 @@ const segments = [ "conversion_lag_bucket", "conversion_or_adjustment_lag_bucket", "date", - "day_of_week", "device", "external_conversion_source", "keyword.ad_group_criterion", "keyword.info.match_type", "keyword.info.text", - "month", - "month_of_year", "new_versus_returning_customers", - "quarter", "slot", - "week", - "year", -]; +].map((f) => getOption(f, "segments")); const metrics = [ "absolute_top_impression_percentage", @@ -319,7 +315,7 @@ const metrics = [ "video_view_rate", "video_views", "view_through_conversions", -]; +].map((f) => getOption(f, "metrics")); const resourceOption = { label: "Ad", diff --git a/components/google_ads/common/resources/adGroup.mjs b/components/google_ads/common/resources/adGroup.mjs index 0f38b45ad99af..7958d432c5a24 100644 --- a/components/google_ads/common/resources/adGroup.mjs +++ b/components/google_ads/common/resources/adGroup.mjs @@ -1,3 +1,5 @@ +import { getOption } from "../utils.mjs"; + const fields = [ "ad_rotation_mode", "audience_setting.use_audience_grouped", @@ -31,7 +33,7 @@ const fields = [ "tracking_url_template", "type", "url_custom_parameters", -]; +].map((f) => getOption(f, "ad_group")); const segments = [ "ad_destination_type", @@ -47,18 +49,12 @@ const segments = [ "conversion_lag_bucket", "conversion_or_adjustment_lag_bucket", "date", - "day_of_week", "device", "external_conversion_source", "hour", - "month", - "month_of_year", "new_versus_returning_customers", - "quarter", "slot", - "week", - "year", -]; +].map((f) => getOption(f, "segments")); const metrics = [ "absolute_top_impression_percentage", @@ -159,7 +155,7 @@ const metrics = [ "video_view_rate", "video_views", "view_through_conversions", -]; +].map((f) => getOption(f, "metrics")); const resourceOption = { label: "Ad Group", diff --git a/components/google_ads/common/resources/campaign.mjs b/components/google_ads/common/resources/campaign.mjs index bb561c496d26a..a05729e8fe24e 100644 --- a/components/google_ads/common/resources/campaign.mjs +++ b/components/google_ads/common/resources/campaign.mjs @@ -1,3 +1,5 @@ +import { getOption } from "../utils.mjs"; + const fields = [ "accessible_bidding_strategy", "ad_serving_optimization_status", @@ -93,7 +95,7 @@ const fields = [ "vanity_pharma.vanity_pharma_display_url_mode", "vanity_pharma.vanity_pharma_text", "video_brand_safety_suitability", -]; +].map((f) => getOption(f, "campaign")); const segments = [ "ad_destination_type", @@ -111,14 +113,10 @@ const segments = [ "conversion_or_adjustment_lag_bucket", "conversion_value_rule_primary_dimension", "date", - "day_of_week", "device", "external_conversion_source", "hour", - "month", - "month_of_year", "new_versus_returning_customers", - "quarter", "recommendation_type", "sk_ad_network_ad_event_type", "sk_ad_network_attribution_credit", @@ -130,9 +128,7 @@ const segments = [ "sk_ad_network_source_type", "sk_ad_network_user_type", "slot", - "week", - "year", -]; +].map((f) => getOption(f, "segments")); const metrics = [ "absolute_top_impression_percentage", @@ -274,7 +270,7 @@ const metrics = [ "view_through_conversions_from_location_asset_other_engagement", "view_through_conversions_from_location_asset_store_visits", "view_through_conversions_from_location_asset_website", -]; +].map((f) => getOption(f, "metrics")); const resourceOption = { label: "Campaign", diff --git a/components/google_ads/common/resources/customer.mjs b/components/google_ads/common/resources/customer.mjs index 1ed7ad6c9cf6f..f29f3288970fe 100644 --- a/components/google_ads/common/resources/customer.mjs +++ b/components/google_ads/common/resources/customer.mjs @@ -1,3 +1,5 @@ +import { getOption } from "../utils.mjs"; + const fields = [ "auto_tagging_enabled", "call_reporting_setting.call_conversion_action", @@ -32,7 +34,7 @@ const fields = [ "time_zone", "tracking_url_template", "video_brand_safety_suitability", -]; +].map((f) => getOption(f, "customer")); const segments = [ "ad_network_type", @@ -46,14 +48,10 @@ const segments = [ "conversion_or_adjustment_lag_bucket", "conversion_value_rule_primary_dimension", "date", - "day_of_week", "device", "external_conversion_source", "hour", - "month", - "month_of_year", "new_versus_returning_customers", - "quarter", "recommendation_type", "sk_ad_network_ad_event_type", "sk_ad_network_attribution_credit", @@ -65,9 +63,7 @@ const segments = [ "sk_ad_network_source_type", "sk_ad_network_user_type", "slot", - "week", - "year", -]; +].map((f) => getOption(f, "segments")); const metrics = [ "absolute_top_impression_percentage", @@ -166,7 +162,7 @@ const metrics = [ "view_through_conversions_from_location_asset_other_engagement", "view_through_conversions_from_location_asset_store_visits", "view_through_conversions_from_location_asset_website", -]; +].map((f) => getOption(f, "metrics")); const resourceOption = { label: "Customer", diff --git a/components/google_ads/common/utils.mjs b/components/google_ads/common/utils.mjs index b7561023a4196..905162bb8dbf1 100644 --- a/components/google_ads/common/utils.mjs +++ b/components/google_ads/common/utils.mjs @@ -30,3 +30,19 @@ export function parseStringObject(value = "{}") { **${err.toString()}**`); } } + +export function getOption(label, prefix) { + return { + label, + value: `${prefix}.${label}`, + }; +} + +export function checkPrefix(value, prefix) { + const checkStr = (s) => s && (s?.startsWith?.(prefix) + ? s + : `${prefix}.${s}`); + return Array.isArray(value ?? []) + ? (value ?? []).map(checkStr) + : checkStr(value); +} diff --git a/components/google_ads/google_ads.app.mjs b/components/google_ads/google_ads.app.mjs index 46db697515c3a..3242829750418 100644 --- a/components/google_ads/google_ads.app.mjs +++ b/components/google_ads/google_ads.app.mjs @@ -128,7 +128,7 @@ export default { }, ...args, }); - return response.results; + return response; }, async listAccessibleCustomers() { const response = await this._makeRequest({ @@ -139,13 +139,15 @@ export default { async listCustomerClients({ query, ...args }) { - return this.search({ + const { results } = await this.search({ query: QUERIES.listCustomerClients(query), ...args, }); + return results; }, async createReport(args) { - return this.search(args); + const { results } = await this.search(args); + return results; }, async createUserList(args) { const response = await this._makeRequest({ @@ -156,44 +158,62 @@ export default { return response; }, async listUserLists(args) { - return this.search({ + const { results } = await this.search({ query: QUERIES.listUserLists(), ...args, }); + return results; }, async listConversionActions(args) { - return this.search({ + const { results } = await this.search({ query: QUERIES.listConversionActions(), ...args, }); + return results; }, async listRemarketingActions(args) { - return this.search({ + const { results } = await this.search({ query: QUERIES.listRemarketingActions(), ...args, }); + return results; }, async listLeadForms(args) { - return this.search({ + const { results } = await this.search({ query: QUERIES.listLeadForms(), ...args, }); + return results; }, async listCampaigns({ query, ...args }) { - return this.search({ + const { results } = await this.search({ query: QUERIES.listCampaigns(query), ...args, }); + return results; + }, + async listResources({ + resource, query, pageToken, ...args + }) { + return this.search({ + query: QUERIES.listResources(resource, query), + params: { + pageSize: 100, + pageToken, + }, + ...args, + }); }, async getLeadFormData({ leadFormId, ...args }) { - return this.search({ + const { results } = await this.search({ query: QUERIES.listLeadFormSubmissionData(leadFormId), ...args, }); + return results; }, async createConversionAction(args) { const response = await this._makeRequest({ diff --git a/components/google_ads/package.json b/components/google_ads/package.json index 4f6be6debf72f..d754908c253dd 100644 --- a/components/google_ads/package.json +++ b/components/google_ads/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/google_ads", - "version": "0.2.1", + "version": "0.3.0", "description": "Pipedream Google Ads Components", "main": "google_ads.app.mjs", "keywords": [ diff --git a/components/google_ads/sources/new-campaign-created/new-campaign-created.mjs b/components/google_ads/sources/new-campaign-created/new-campaign-created.mjs index 39fca7e9bc040..54ec142dbdc32 100644 --- a/components/google_ads/sources/new-campaign-created/new-campaign-created.mjs +++ b/components/google_ads/sources/new-campaign-created/new-campaign-created.mjs @@ -7,7 +7,7 @@ export default { key: "google_ads-new-campaign-created", name: "New Campaign Created", description: "Emit new event when a new campaign is created. [See the documentation](https://developers.google.com/google-ads/api/fields/v16/campaign)", - version: "0.0.1", + version: "0.0.2", type: "source", dedupe: "unique", sampleEmit, @@ -24,8 +24,8 @@ export default { options: campaign.fields, optional: true, default: [ - "id", - "name", + "campaign.id", + "campaign.name", ], }, }, diff --git a/components/google_ads/sources/new-lead-form-entry/new-lead-form-entry.mjs b/components/google_ads/sources/new-lead-form-entry/new-lead-form-entry.mjs index df7fc79e7a006..39e15bfedca6f 100644 --- a/components/google_ads/sources/new-lead-form-entry/new-lead-form-entry.mjs +++ b/components/google_ads/sources/new-lead-form-entry/new-lead-form-entry.mjs @@ -8,7 +8,7 @@ export default { key: "google_ads-new-lead-form-entry", name: "New Lead Form Entry", description: "Emit new event for new leads on a Lead Form. [See the documentation](https://developers.google.com/google-ads/api/fields/v16/lead_form_submission_data)", - version: "0.0.1", + version: "0.0.2", type: "source", dedupe: "unique", sampleEmit,