diff --git a/lib/actions/google/ads/lib/ads_executor.d.ts b/lib/actions/google/ads/lib/ads_executor.d.ts index a531a629..54decbb7 100644 --- a/lib/actions/google/ads/lib/ads_executor.d.ts +++ b/lib/actions/google/ads/lib/ads_executor.d.ts @@ -6,6 +6,8 @@ export declare class GoogleAdsActionExecutor { readonly targetCid: any; readonly mobileAppId: any; readonly uploadKeyType: string; + readonly consentAdUserData: any; + readonly consentAdPersonalization: any; offlineUserDataJobResourceName: string; targetUserListRN: string; constructor(adsRequest: GoogleAdsActionRequest); diff --git a/lib/actions/google/ads/lib/ads_executor.js b/lib/actions/google/ads/lib/ads_executor.js index e0799c16..687e072b 100644 --- a/lib/actions/google/ads/lib/ads_executor.js +++ b/lib/actions/google/ads/lib/ads_executor.js @@ -19,6 +19,8 @@ class GoogleAdsActionExecutor { this.targetCid = this.adsRequest.targetCid; this.mobileAppId = this.adsRequest.mobileAppId; this.uploadKeyType = this.adsRequest.uploadKeyType; + this.consentAdUserData = this.adsRequest.consentAdUserData; + this.consentAdPersonalization = this.adsRequest.consentAdPersonalization; this.offlineUserDataJobResourceName = ""; // will be updated later this.targetUserListRN = adsRequest.formParams.targetUserListRN; } @@ -32,7 +34,7 @@ class GoogleAdsActionExecutor { } createDataJob() { return __awaiter(this, void 0, void 0, function* () { - const createJobResp = yield this.apiClient.createDataJob(this.targetCid, this.targetUserListRN); + const createJobResp = yield this.apiClient.createDataJob(this.targetCid, this.targetUserListRN, this.consentAdUserData, this.consentAdPersonalization); this.offlineUserDataJobResourceName = createJobResp.resourceName; this.log("info", "Created data job:", this.offlineUserDataJobResourceName); return createJobResp; diff --git a/lib/actions/google/ads/lib/ads_form_builder.d.ts b/lib/actions/google/ads/lib/ads_form_builder.d.ts index df93a9fd..4ec5aaf5 100644 --- a/lib/actions/google/ads/lib/ads_form_builder.d.ts +++ b/lib/actions/google/ads/lib/ads_form_builder.d.ts @@ -127,6 +127,17 @@ export declare class GoogleAdsActionFormBuilder { default: string; required: boolean; }; + consentSetting(): { + name: string; + label: string; + type: "select"; + options: { + name: string; + label: string; + }[]; + default: string; + required: boolean; + }[]; private maybeSetLoginCustomer; private maybeSetTargetCustomer; private getLoginCidOptions; diff --git a/lib/actions/google/ads/lib/ads_form_builder.js b/lib/actions/google/ads/lib/ads_form_builder.js index 3dd9d433..5c949d8e 100644 --- a/lib/actions/google/ads/lib/ads_form_builder.js +++ b/lib/actions/google/ads/lib/ads_form_builder.js @@ -67,16 +67,24 @@ class GoogleAdsActionFormBuilder { } // 3) Branch 1: Show the fields for creating a new list (name & desc), plus hashing, and we're done if (this.isCreate) { - form.fields.push(this.newListNameField()); - form.fields.push(this.newListDescriptionField()); - form.fields.push(this.doHashingFormField()); + form.fields = [ + ...form.fields, + this.newListNameField(), + this.newListDescriptionField(), + this.doHashingFormField(), + ...this.consentSetting(), + ]; return form; } // 4) Branch 2: Select an existing list, but we need to check that there is at least one to choose... const userListOptions = yield this.getUserListOptions(); if (userListOptions.length) { - form.fields.push(this.targetListField(userListOptions)); - form.fields.push(this.doHashingFormField()); + form.fields = [ + ...form.fields, + this.targetListField(userListOptions), + this.doHashingFormField(), + ...this.consentSetting(), + ]; } else { form.fields.push(this.noAvailableListsField()); @@ -250,6 +258,34 @@ class GoogleAdsActionFormBuilder { required: true, }; } + consentSetting() { + return [ + { + name: "consentAdUserData", + label: "Step 5) The consent setting for consent for ad user data", + type: "select", + options: [ + { name: "UNSPECIFIED", label: "Unspecified" }, + { name: "GRANTED", label: "Granted" }, + { name: "DENIED", label: "Denied" }, + ], + default: "UNSPECIFIED", + required: true, + }, + { + name: "consentAdPersonalization", + label: "The consent setting for consent for ad personalization", + type: "select", + options: [ + { name: "UNSPECIFIED", label: "Unspecified" }, + { name: "GRANTED", label: "Granted" }, + { name: "DENIED", label: "Denied" }, + ], + default: "UNSPECIFIED", + required: true, + }, + ]; + } maybeSetLoginCustomer() { return __awaiter(this, void 0, void 0, function* () { if (!this.loginCid) { diff --git a/lib/actions/google/ads/lib/ads_request.d.ts b/lib/actions/google/ads/lib/ads_request.d.ts index aaade0b3..bc050cc2 100644 --- a/lib/actions/google/ads/lib/ads_request.d.ts +++ b/lib/actions/google/ads/lib/ads_request.d.ts @@ -26,6 +26,8 @@ export declare class GoogleAdsActionRequest { get isMobileDevice(): boolean; get mobileAppId(): any; get uploadKeyType(): "MOBILE_ADVERTISING_ID" | "CONTACT_INFO"; + get consentAdUserData(): any; + get consentAdPersonalization(): any; get developerToken(): string; get doHashingBool(): boolean; get isCreate(): boolean; diff --git a/lib/actions/google/ads/lib/ads_request.js b/lib/actions/google/ads/lib/ads_request.js index e56011b8..8858c658 100644 --- a/lib/actions/google/ads/lib/ads_request.js +++ b/lib/actions/google/ads/lib/ads_request.js @@ -80,6 +80,12 @@ class GoogleAdsActionRequest { get uploadKeyType() { return this.isMobileDevice ? "MOBILE_ADVERTISING_ID" : "CONTACT_INFO"; } + get consentAdUserData() { + return this.formParams.consentAdUserData; + } + get consentAdPersonalization() { + return this.formParams.consentAdPersonalization; + } get developerToken() { return this.actionInstance.developerToken; } diff --git a/lib/actions/google/ads/lib/api_client.d.ts b/lib/actions/google/ads/lib/api_client.d.ts index e73e4eee..aa611fc5 100644 --- a/lib/actions/google/ads/lib/api_client.d.ts +++ b/lib/actions/google/ads/lib/api_client.d.ts @@ -1,4 +1,5 @@ import { Logger } from "../../common/logger"; +type ConsentType = "UNSPECIFIED" | "GRANTED" | "DENIED"; export declare class GoogleAdsApiClient { readonly log: Logger; readonly accessToken: string; @@ -9,8 +10,9 @@ export declare class GoogleAdsApiClient { searchOpenUserLists(clientCid: string, uploadKeyType: "MOBILE_ADVERTISING_ID" | "CONTACT_INFO"): Promise; searchClientCustomers(clientCid: string): Promise; createUserList(targetCid: string, newListName: string, newListDescription: string, uploadKeyType: "MOBILE_ADVERTISING_ID" | "CONTACT_INFO", mobileAppId?: string): Promise; - createDataJob(targetCid: string, userListResourceName: string): Promise; + createDataJob(targetCid: string, userListResourceName: string, consentAdUserData: ConsentType, consentAdPersonalization: ConsentType): Promise; addDataJobOperations(offlineUserDataJobResourceName: string, userIdentifiers: any[]): Promise; runJob(offlineUserDataJobResourceName: string): Promise; apiCall(method: "GET" | "POST", url: string, data?: any): Promise; } +export {}; diff --git a/lib/actions/google/ads/lib/api_client.js b/lib/actions/google/ads/lib/api_client.js index 8e45045e..7d7c4bf4 100644 --- a/lib/actions/google/ads/lib/api_client.js +++ b/lib/actions/google/ads/lib/api_client.js @@ -90,10 +90,14 @@ class GoogleAdsApiClient { return this.apiCall(method, path, body); }); } - createDataJob(targetCid, userListResourceName) { + createDataJob(targetCid, userListResourceName, consentAdUserData, consentAdPersonalization) { return __awaiter(this, void 0, void 0, function* () { const method = "POST"; const path = `customers/${targetCid}/offlineUserDataJobs:create`; + const consent = { + ad_user_data: consentAdUserData, + ad_personalization: consentAdPersonalization, + }; const body = { customer_id: targetCid, job: { @@ -101,6 +105,7 @@ class GoogleAdsApiClient { type: "CUSTOMER_MATCH_USER_LIST", customer_match_user_list_metadata: { user_list: userListResourceName, + consent, }, }, }; @@ -143,7 +148,7 @@ class GoogleAdsApiClient { url, data, headers, - baseURL: "https://googleads.googleapis.com/v14/", + baseURL: "https://googleads.googleapis.com/v15/", }); if (process.env.ACTION_HUB_DEBUG) { const apiResponse = lodash.cloneDeep(response); diff --git a/src/actions/google/ads/lib/ads_executor.ts b/src/actions/google/ads/lib/ads_executor.ts index 1299eea2..12753958 100644 --- a/src/actions/google/ads/lib/ads_executor.ts +++ b/src/actions/google/ads/lib/ads_executor.ts @@ -8,6 +8,8 @@ export class GoogleAdsActionExecutor { readonly targetCid = this.adsRequest.targetCid readonly mobileAppId = this.adsRequest.mobileAppId readonly uploadKeyType = this.adsRequest.uploadKeyType + readonly consentAdUserData = this.adsRequest.consentAdUserData + readonly consentAdPersonalization = this.adsRequest.consentAdPersonalization offlineUserDataJobResourceName: string targetUserListRN: string @@ -25,7 +27,12 @@ export class GoogleAdsActionExecutor { } async createDataJob() { - const createJobResp = await this.apiClient.createDataJob(this.targetCid, this.targetUserListRN) + const createJobResp = await this.apiClient.createDataJob( + this.targetCid, + this.targetUserListRN, + this.consentAdUserData, + this.consentAdPersonalization, + ) this.offlineUserDataJobResourceName = createJobResp.resourceName this.log("info", "Created data job:", this.offlineUserDataJobResourceName) return createJobResp diff --git a/src/actions/google/ads/lib/ads_form_builder.ts b/src/actions/google/ads/lib/ads_form_builder.ts index b5b1c600..1aafc6fb 100644 --- a/src/actions/google/ads/lib/ads_form_builder.ts +++ b/src/actions/google/ads/lib/ads_form_builder.ts @@ -67,17 +67,25 @@ export class GoogleAdsActionFormBuilder { // 3) Branch 1: Show the fields for creating a new list (name & desc), plus hashing, and we're done if (this.isCreate) { - form.fields.push(this.newListNameField()) - form.fields.push(this.newListDescriptionField()) - form.fields.push(this.doHashingFormField()) + form.fields = [ + ...form.fields, + this.newListNameField(), + this.newListDescriptionField(), + this.doHashingFormField(), + ...this.consentSetting(), + ] return form } // 4) Branch 2: Select an existing list, but we need to check that there is at least one to choose... const userListOptions = await this.getUserListOptions() if (userListOptions.length) { - form.fields.push(this.targetListField(userListOptions)) - form.fields.push(this.doHashingFormField()) + form.fields = [ + ...form.fields, + this.targetListField(userListOptions), + this.doHashingFormField(), + ...this.consentSetting(), + ] } else { form.fields.push(this.noAvailableListsField()) } @@ -261,6 +269,35 @@ export class GoogleAdsActionFormBuilder { } } + consentSetting() { + return [ + { + name: "consentAdUserData", + label: "Step 5) The consent setting for consent for ad user data", + type: "select" as "select", + options: [ + {name: "UNSPECIFIED", label: "Unspecified"}, + {name: "GRANTED", label: "Granted"}, + {name: "DENIED", label: "Denied"}, + ], + default: "UNSPECIFIED", + required: true, + }, + { + name: "consentAdPersonalization", + label: "The consent setting for consent for ad personalization", + type: "select" as "select", + options: [ + {name: "UNSPECIFIED", label: "Unspecified"}, + {name: "GRANTED", label: "Granted"}, + {name: "DENIED", label: "Denied"}, + ], + default: "UNSPECIFIED", + required: true, + }, + ] + } + private async maybeSetLoginCustomer() { if (!this.loginCid) { return diff --git a/src/actions/google/ads/lib/ads_request.ts b/src/actions/google/ads/lib/ads_request.ts index d03e3579..0778e411 100644 --- a/src/actions/google/ads/lib/ads_request.ts +++ b/src/actions/google/ads/lib/ads_request.ts @@ -101,6 +101,14 @@ export class GoogleAdsActionRequest { return this.isMobileDevice ? "MOBILE_ADVERTISING_ID" : "CONTACT_INFO" } + get consentAdUserData() { + return this.formParams.consentAdUserData + } + + get consentAdPersonalization() { + return this.formParams.consentAdPersonalization + } + get developerToken() { return this.actionInstance.developerToken } diff --git a/src/actions/google/ads/lib/api_client.ts b/src/actions/google/ads/lib/api_client.ts index 0fa8ed40..03f47ea9 100644 --- a/src/actions/google/ads/lib/api_client.ts +++ b/src/actions/google/ads/lib/api_client.ts @@ -3,6 +3,12 @@ import * as lodash from "lodash" import { sanitizeError as sanitize } from "../../common/error_utils" import { Logger } from "../../common/logger" +type ConsentType = "UNSPECIFIED" | "GRANTED" | "DENIED" +interface Consent { + ad_user_data: ConsentType + ad_personalization: ConsentType +} + export class GoogleAdsApiClient { constructor(readonly log: Logger, readonly accessToken: string @@ -77,9 +83,18 @@ export class GoogleAdsApiClient { return this.apiCall(method, path, body) } - async createDataJob(targetCid: string, userListResourceName: string) { + async createDataJob( + targetCid: string, + userListResourceName: string, + consentAdUserData: ConsentType, + consentAdPersonalization: ConsentType, + ) { const method = "POST" const path = `customers/${targetCid}/offlineUserDataJobs:create` + const consent: Consent = { + ad_user_data: consentAdUserData, + ad_personalization: consentAdPersonalization, + } const body = { customer_id: targetCid, job: { @@ -87,6 +102,7 @@ export class GoogleAdsApiClient { type: "CUSTOMER_MATCH_USER_LIST", customer_match_user_list_metadata: { user_list: userListResourceName, + consent, }, }, } @@ -129,7 +145,7 @@ export class GoogleAdsApiClient { url, data, headers, - baseURL: "https://googleads.googleapis.com/v14/", + baseURL: "https://googleads.googleapis.com/v15/", }) if (process.env.ACTION_HUB_DEBUG) {