Posts tagged with google-cloud-platform

I'm using the following Google Apps Script to pull in data using the Google Ads API. The data appears to be pulling in fine, but parsing it seems to be an issue. Here is the script:

 function getGoogleAdsData() {   const fileId = 'fileID';   const campaignSheetName = 'Campaigns';   const campaignMetrics = ['segments.date', 'campaign.id', 'metrics.cost_micros'];   // Calculate the dates for the previous week (Monday to Sunday)   const today = new Date();   const dayOfWeek = today.getDay(); // 0 (Sunday) to 6 (Saturday)      // Adjust today to get the last Sunday   const lastSunday = new Date(today);   lastSunday.setDate(today.getDate() - dayOfWeek - 0);   // Calculate the previous Monday (6 days before last Sunday)   const lastMonday = new Date(lastSunday);   lastMonday.setDate(lastSunday.getDate() - 6);   const formattedStartDate = Utilities.formatDate(lastMonday, Session.getScriptTimeZone(), 'yyyy-MM-dd');   const formattedEndDate = Utilities.formatDate(lastSunday, Session.getScriptTimeZone(), 'yyyy-MM-dd');      Logger.log(`Fetching data from ${formattedStartDate} to ${formattedEndDate}`);   // Fetch Campaign Data   const campaignData = fetchGoogleAdsData(formattedStartDate, formattedEndDate, campaignMetrics);   const formattedCampaignData = campaignData.map(row => [     formatDate(row[0]), // Date     row[1], // Campaign ID     row[2] // Cost in currency units   ]);      // Write to Sheets if there's data   if (formattedCampaignData.length > 0) {     writeToSheet(fileId, campaignSheetName, formattedCampaignData, ['Date', 'Campaign ID', 'Cost']);   } else {     Logger.log('No data available to write to the sheet.');   } } function fetchGoogleAdsData(startDate, endDate, metrics) {   const query = `     SELECT ${metrics.join(', ')}     FROM campaign     WHERE segments.date BETWEEN '${startDate}' AND '${endDate}'   `;      const apiUrl = 'https://googleads.googleapis.com/v17/customers/xxxxxxxx/googleAds:searchStream';   const options = {     method: 'POST',     headers: {       'Authorization': `Bearer ${ScriptApp.getOAuthToken()}`,        'developer-token': 'OUR-TOKEN',       'login-customer-id': 'xxxxxxxxxx'     },     contentType: 'application/json',     payload: JSON.stringify({ query: query }),     muteHttpExceptions: true   };      const response = UrlFetchApp.fetch(apiUrl, options);   const responseData = JSON.parse(response.getContentText());   Logger.log(`Full API Response: ${JSON.stringify(responseData)}`);   if (responseData.length > 0 && responseData[0].results) {     Logger.log(`Found ${responseData[0].results.length} result(s).`);     return responseData[0].results.map(result => {       Logger.log(`Metrics object: ${JSON.stringify(result.metrics)}`);       const row = [];       metrics.forEach(metric => {         let value = getNestedValue(result, metric.split('.'));         Logger.log(`Metric: ${metric}, Value: ${value}`); // Log each metric's value         if (metric === 'metrics.cost_micros') {           value = value / 10; // Convert micros to currency units           Logger.log(`Converted cost_micros: ${value}`);         }         row.push(value);       });       return row;     });   } else {     Logger.log('No results found in response or response structure is different.');     return [];   } } function getNestedValue(obj, keys) {   return keys.reduce((acc, key) => (acc && acc[key] !== undefined) ? acc[key] : null, obj); } function writeToSheet(fileId, sheetName, data, headers) {   const sheet = SpreadsheetApp.openById(fileId).getSheetByName(sheetName);   sheet.clear(); // Clear existing data   sheet.appendRow(headers); // Add headers   if (data.length > 0 && data[0].length > 0) {     sheet.getRange(2, 1, data.length, data[0].length).setValues(data); // Add data   } else {     Logger.log('No data to write to the sheet.');   } } function formatDate(dateString) {   const date = new Date(dateString);   return Utilities.formatDate(date, Session.getScriptTimeZone(), 'dd/MM/yyyy'); } 

The full API response looks something like this:

Full API Response: [{"results":[{"campaign":{"resourceName":"customers/xxxxx/campaigns/3509213","id":"3509213"},"metrics":{"costMicros":"41060000"},"segments":{"date":"2024-08-26"}},{"campaign":{"resourceName":"customers/xxxxx/campaigns/3509213","id":"3509213"},"metrics":{"costMicros":"54460000"},"segments":{"date":"2024-08-27"}},{"campaign":{"resourceName":"customers/xxxxx/campaigns/3509213","id":"3509213"},"metrics":{"costMicros":"33690000"},"segments":{"date":"2024-08-28"}}..... 

And further logging shows this:

8:05:38AM  Info    Found 232 result(s). 8:05:38AM  Info    Metrics object: {"costMicros":"41060000"} 8:05:38AM  Info    Metric: segments.date, Value: 2024-08-26 8:05:38AM  Info    Metric: campaign.id, Value: 3509213 8:05:38AM  Info    Metric: metrics.cost_micros, Value: null 8:05:38AM  Info    Converted cost_micros: 0 

I've tried changing the metrics.cost_micros to metrics.costMicros, but this doesn't work and returns an unrecognised field.

Why is 'Metric: metrics.cost_micros, Value: null'?

I hope this is in the correct section of the forum, if not an administrator can move the post to the correct place.

Our organization has an existing Google Adwords (G4) that currently a few members of our organization is using and administrating.

This instance of Google Adwords (G4) is currently not connected to our organization.

We want to setup SSO with Azure AD but for this to work the Adwords need to be connected to the organization.

How can we do this? Do we need to create a new google cloud organization and then integrate/connect it to the current Adwords - Or do we need to migrate everything to the new google cloud organization?

We have tried finding the answers for this in the Google Adwords account and in the Google Cloud organizational account, but have not found any information. Is there anyone that has done this or have any recommendations regarding this?

I need to create a report on asset performance from google ads transfer data in bigquery. The report should be similar to the one in Google Ads UI (under Assets with assets names, campaigns, clicks etc). I have enabled PMAX within the bigquery data transfer and now have views about Assets (e.g. ads_Assets, ads_CampaignAssetStats etc) When I query ads_CampaignAssetStats I see an asset field campaign_asset_asset of format customers/1111111111/assets/111111111111, I would like to somehow get the name of this particular asset, but I cannot find this in any asset related tables.

So either that data is not available in BQ data transfer and I need to get it from the api or I am not searching or understanding the data. Where can I find this data within BQ data transfer tables (if at all?

I have looked through google ads data transfer documentation expecting some more insights into the asset views but it didn't clarify how I can get the names of the assets.

I have browsed through other tables within BQ google ads data transfer but haven't found a way to connect the data.

I have a problem setting up a data transfer from SA360 (the old api) to Big Query.

In SA 360 I've two subaccounts. I set up the data transfer for one sub-account without problems, and the data syncs to BigQuery.

The other sub-account causes an error:

PERMISSION_DENIED: Error from API in SendRequestReportRequest for table 'AccountStats'. Error code: conditionNotMet. Message: Permission denied: the SA360 user does not have read access to the report scope.

I'm an admin of both of sub-accounts and use the correct agency and advertising IDs.

Has anyone encountered a problem like this before? Any clues on how to solve the problem are appreciated.

I tried to spot the difference between the set-ups but there is nothing that would differentiate them apart from the agency id and advertising id.

I've tried with this documentation authenticate and with this REST to send the conversion over REST API due to missing Typescript/Javascript-Client-Bib.

I am already failing at the authentication. I hope, if the authentication is working, the click conversion will be send. For the case I am guessing wrong, I extended the question. I hope the question is not to big. Blame me if I am wrong.

This is my error:

My setup Regarding to the documentation I have a google ads account with a developer token. This token will be used, when I send the click conversion, as you can see here. The token has nothing to do with the authentication of the service account. Therefore I have a service account on the Google Cloud Project, which also has the Google Ads Api enabled.

I also added to the workspace domain to the domain wide delegation the client id of the service account with the scope https://www.googleapis.com/auth/adwords This created in the category APIs & Services also an OAuth 2.0 Client IDs The clientIds are fitting as well.

Just in case I also added an OAuth consent screen with the scope https://www.googleapis.com/auth/adwords with the Testing status and external. But I don't think I will need this with a service account.

The service account itself has no further related rights. The documentation don't give me the info, that the service account need further rights. My thoughts: I added the client id to the domain wide delegation, this should be enough. I hope I am wrong here.

Now everything should be set up. I hope I didn't miss a step.

My guess: Either I am missing some rights. Or I missunderstand the refresh token in the function authenticateToGoogleAdsManager. I create a signed JWT. Google says here, I need a refresh token. But the authentication via await fetch('https://oauth2.googleapis.com/token' just gives me an access token. So I thought I just need a jwt here.

This is the way I am executing my code (in a testcase. Service Account JSON and clickConversion are given.)

 // First I create a signed jwt     const jwt = generateJsonWebTokenForServiceAccount(       serviceAccount,       ['https://www.googleapis.com/auth/adwords'],       'googleads'     )     // Then I use the signed jwt to authenticate to Google Ads Manager     const authenticationResult = await authenticateToGoogleAdsManager(       serviceAccount.client_id,       serviceAccount.private_key,       jwt     )     console.log(authenticationResult)     // Then I use the access token to send a click conversion to Google Ads Manager     const test = await sendClickConversionToGoogleAdsManager(       CUSTOMERID,       clickConversion,       accessToken.access_token,       'DEV-TOKEN'     ) 

Here are my functions:

/**  * Generates a JSON Web Token (JWT) for a service account.  *  * @param serviceAccount - The service account object containing the client email and private key.  * @param scopes - An array of scopes for which the token will be authorized.  * @param serviceName - The name of the service for which the token will be authorized. Default is 'oauth2'.  * @param expirationTimeInSeconds - The expiration time of the token in seconds. Default is 3600 seconds (1 hour).  * @returns The generated JSON Web Token.  */ export function generateJsonWebTokenForServiceAccount(serviceAccount: ServiceAccount,   scopes: string[],   serviceName: string = 'oauth2',   expirationTimeInSeconds = 3600) {   const aud =     serviceName === 'oauth2' ? 'https://oauth2.googleapis.com/token' : `https://${serviceName}.googleapis.com/`   const currentTimestamp = Math.floor(Date.now() / 1000)   const expirationTimestamp = currentTimestamp + expirationTimeInSeconds   const payload = {     iss: serviceAccount.client_email,     sub: serviceAccount.client_email,     scope: scopes.join(' '),     aud: aud,     exp: expirationTimestamp,     iat: currentTimestamp   }   const options: SignOptions = {     algorithm: 'RS256'   }   return jwt.sign(payload, serviceAccount.private_key, options) } /**  * Authenticates to Google Ads Manager using the provided credentials.  * @param clientId The client ID for authentication.  * @param clientSecret The client secret for authentication.  * @param refreshToken The refresh token for authentication.  * @returns A promise that resolves to the access token.  */ export async function authenticateToGoogleAdsManager(clientId: string,   clientSecret: string,   refreshToken: string): Promise<string> {   const url = 'https://www.googleapis.com/oauth2/v3/token'   const body = {     client_id: clientId,     client_secret: clientSecret,     refresh_token: refreshToken,     grant_type: 'refresh_token'   }   const response = await fetch(url, {     method: 'POST',     body: JSON.stringify(body)   })   const data = await response.json()   return data.access_token } /**  * Sends a click conversion to Google Ads Manager.  *   * @param customerId - The ID of the customer.  * @param clickConversion - The click conversion data.  * @param accessToken - The access token for authentication.  * @param developerToken - The developer token for authentication.  * @param options - Optional API options.  * @returns A promise that resolves to void.  */ export async function sendClickConversionToGoogleAdsManager(customerId: number,   clickConversion: ClickConversion,   accessToken: string,   developerToken: string,   options?: ApiOptions): Promise<void> {   const url = `https://googleads.googleapis.com/v15/customers/${customerId}:uploadClickConversions`   if (!options) {     options = {       partialFailure: false,       validateOnly: false,       debugEnabled: false,       jobId: 0     }   }   const response = await fetch(url, {     method: 'POST',     headers: {       'Content-Type': 'application/json',       Authorization: `Bearer ${accessToken}`,       'developer-token': developerToken     },     body: JSON.stringify({       conversions: [clickConversion],       partialFailure: options.partialFailure,       validateOnly: options.validateOnly,       debugEnabled: options.debugEnabled,       jobId: options.jobId     })   })   const data = await response.json()   return data }