From 04bffaad9556198e4aa40eebd9e479ddf2473376 Mon Sep 17 00:00:00 2001 From: Josh Radcliff Date: Wed, 24 Jun 2026 15:44:06 -0400 Subject: [PATCH 1/2] feat(sample): use CompositeData with IP for audiences Change-Id: Ie683a9d14dbdf8fda4d4062185713dbc840c896d --- package-lock.json | 24 +++--- samples/audiences/ingest_audience_members.ts | 80 +++++++++++++++++++- samples/sampledata/audience_members_1.json | 15 ++++ 3 files changed, 105 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9d7032f..df60b71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -526,9 +526,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.19.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.21.tgz", - "integrity": "sha512-VMeFBSCKQKmm2swI2kW51SFusDqekC6q9trBCvJ/JliDchFSuoYYKN7yVNjPthP1HKZcx3U1gI/wTcEBjEFKTA==", + "version": "22.20.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.0.tgz", + "integrity": "sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -762,9 +762,9 @@ } }, "node_modules/@ungap/structured-clone": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", - "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.2.tgz", + "integrity": "sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==", "dev": true, "license": "ISC" }, @@ -4120,9 +4120,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", - "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -4874,9 +4874,9 @@ "license": "ISC" }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "license": "MIT", "dependencies": { "cliui": "^8.0.1", diff --git a/samples/audiences/ingest_audience_members.ts b/samples/audiences/ingest_audience_members.ts index 456daa6..3b65701 100644 --- a/samples/audiences/ingest_audience_members.ts +++ b/samples/audiences/ingest_audience_members.ts @@ -19,11 +19,13 @@ import {IngestionServiceClient} from '@google-ads/datamanager'; import {protos} from '@google-ads/datamanager'; const { AudienceMember, + CompositeData, Destination, Encoding: DataManagerEncoding, Consent, ConsentStatus, IngestAudienceMembersRequest, + IpData, ProductAccount, TermsOfService, TermsOfServiceStatus, @@ -52,6 +54,11 @@ interface Arguments { interface MemberRow { emails?: string[]; phoneNumbers?: string[]; + ipInfos?: { + ipAddress: string; + observeStartTime?: string; + observeEndTime?: string; + }[]; } /** @@ -167,8 +174,77 @@ async function main() { } } - if (userData.userIdentifiers.length > 0) { - audienceMembers.push(AudienceMember.create({userData: userData})); + // Process IP address information + const ipDatas = []; + for (const ipInfo of memberRow.ipInfos || []) { + const googleAdsAccountTypeValue = ProductAccount.AccountType.GOOGLE_ADS; + if (operatingAccountType !== googleAdsAccountTypeValue) { + console.log( + `Skipping IP address information for operating account type ${operatingAccountType}. ` + + `Sending IP address is only supported for operating account type GOOGLE_ADS.`, + ); + } + + const ipAddress = (ipInfo.ipAddress || '').trim(); + if (!ipAddress) { + console.log('Skipping IP address information with no IP address'); + continue; + } + + const ipData = IpData.create({ipAddress}); + + if (ipInfo.observeStartTime) { + const startTimeStr = ipInfo.observeStartTime.trim(); + if (startTimeStr) { + try { + const date = new Date(startTimeStr); + if (isNaN(date.getTime())) { + throw new Error('Invalid date'); + } + ipData.observeStartTime = { + seconds: Math.floor(date.getTime() / 1000), + nanos: (date.getTime() % 1000) * 1e6, + }; + } catch (e) { + console.log( + `Ignoring observe start time '${startTimeStr}' since it can't be parsed`, + ); + } + } + } + + if (ipInfo.observeEndTime) { + const endTimeStr = ipInfo.observeEndTime.trim(); + if (endTimeStr) { + try { + const date = new Date(endTimeStr); + if (isNaN(date.getTime())) { + throw new Error('Invalid date'); + } + ipData.observeEndTime = { + seconds: Math.floor(date.getTime() / 1000), + nanos: (date.getTime() % 1000) * 1e6, + }; + } catch (e) { + console.log( + `Ignoring observe end time '${endTimeStr}' since it can't be parsed`, + ); + } + } + } + + ipDatas.push(ipData); + } + + if (userData.userIdentifiers.length > 0 || ipDatas.length > 0) { + const compositeData = CompositeData.create(); + if (userData.userIdentifiers.length > 0) { + compositeData.userData = userData; + } + if (ipDatas.length > 0) { + compositeData.ipData = ipDatas; + } + audienceMembers.push(AudienceMember.create({compositeData})); } else { console.warn('Ignoring line. No data.'); } diff --git a/samples/sampledata/audience_members_1.json b/samples/sampledata/audience_members_1.json index f40064d..128e0f9 100644 --- a/samples/sampledata/audience_members_1.json +++ b/samples/sampledata/audience_members_1.json @@ -3,6 +3,14 @@ "emails": [ "dana@example.com", "DanaM@example.com" + ], + "ipInfos": [ + { + "ipAddress": "192.0.2.211" + }, + { + "ipAddress": "198.51.100.14" + } ] }, { @@ -21,6 +29,13 @@ "emails": [ "quinn@CYMBALGROUP.com", "baklavainthebalkans@gmail.com" + ], + "ipInfos": [ + { + "ipAddress": "203.0.113.98", + "observeStartTime": "2026-06-10T20:17:52.0-04:00", + "observeEndTime": "2026-06-17T04:02:04.123-04:00" + } ] }, { From 7fca00a26da4fbe81cd0e78eee21a11bba97beff Mon Sep 17 00:00:00 2001 From: Josh Radcliff Date: Thu, 25 Jun 2026 14:55:38 -0400 Subject: [PATCH 2/2] fix(build): fix dependency issues and account check in sample Change-Id: I7a8eaed83672a4baa5de4dc08f855d208988bd7b --- package-lock.json | 8 ++++---- samples/audiences/ingest_audience_members.ts | 18 +++++++++--------- samples/package.json | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index df60b71..850d2aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -149,9 +149,9 @@ "link": true }, "node_modules/@google-ads/datamanager": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@google-ads/datamanager/-/datamanager-0.1.0.tgz", - "integrity": "sha512-e5QMtQ1JCTYVpljk8vYKjR+q0g7+GKf3gOQ1xRhUeX19LqzWefDZZUbGL5656e8GPj2KkD+p15X0gdcHsPLwHA==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@google-ads/datamanager/-/datamanager-0.5.0.tgz", + "integrity": "sha512-x6zCnt5PkFBjoD+Qo3sehDHM92DJGwiZKSldrGZ9DW9nF+aIotRfnPAoAfrx8f5ed2YfYYcEu7tihiDyRds3zA==", "license": "Apache-2.0", "dependencies": { "google-gax": "^5.0.0" @@ -4981,7 +4981,7 @@ "license": "Apache-2.0", "dependencies": { "@google-ads/data-manager-util": "^0.3.0", - "@google-ads/datamanager": "^0.1.0", + "@google-ads/datamanager": "^0", "csv-parser": "^3.0.0", "yargs": "^17.7.2" }, diff --git a/samples/audiences/ingest_audience_members.ts b/samples/audiences/ingest_audience_members.ts index 3b65701..47246a6 100644 --- a/samples/audiences/ingest_audience_members.ts +++ b/samples/audiences/ingest_audience_members.ts @@ -139,6 +139,13 @@ async function main() { const memberRows: MemberRow[] = await readMemberData(argv.json_file); + // Converts the operating account type argument to the corresponding enum + // so IP address checks can compare enums to enums. + const operatingAccountType = convertToAccountType( + argv.operating_account_type, + 'operating_account_type', + ); + // Builds the audience_members collection for the request. const audienceMembers = []; for (const memberRow of memberRows) { @@ -177,11 +184,10 @@ async function main() { // Process IP address information const ipDatas = []; for (const ipInfo of memberRow.ipInfos || []) { - const googleAdsAccountTypeValue = ProductAccount.AccountType.GOOGLE_ADS; - if (operatingAccountType !== googleAdsAccountTypeValue) { + if (operatingAccountType !== ProductAccount.AccountType.GOOGLE_ADS) { console.log( `Skipping IP address information for operating account type ${operatingAccountType}. ` + - `Sending IP address is only supported for operating account type GOOGLE_ADS.`, + 'Sending IP address is only supported for operating account type GOOGLE_ADS.', ); } @@ -250,12 +256,6 @@ async function main() { } } - // Sets up the Destination. - const operatingAccountType = convertToAccountType( - argv.operating_account_type, - 'operating_account_type', - ); - const destination = Destination.create({ operatingAccount: ProductAccount.create({ accountType: operatingAccountType, diff --git a/samples/package.json b/samples/package.json index 718bef7..d7f5f1f 100644 --- a/samples/package.json +++ b/samples/package.json @@ -17,7 +17,7 @@ "ingest-audience-members": "ts-node audiences/ingest_audience_members.ts" }, "dependencies": { - "@google-ads/datamanager": "^0.1.0", + "@google-ads/datamanager": "^0", "@google-ads/data-manager-util": "^0.3.0", "csv-parser": "^3.0.0", "yargs": "^17.7.2"