BatchJob and SET

118 views
Skip to first unread message

Prototype.co

unread,
Dec 18, 2017, 4:11:31 AM12/18/17
to AdWords API Forum

Hi all,

First of all, please note that for some reason, neither 'Quote' nor the 'Code' functionality appears to be working. Also, it seems that no formatting works whatsoever, for some reason, so sorry about the mess.

I'm sure we are missing something extremely simple, but it is now eating up a lot of time, and was wondering if anyone could point us in the right direction.

What we would like to achieve is replacing the FinalURLs of a number of ExpandedTextAds using BatchJobService and SET.

What we did was take a previously functional code which uses the operator ADD and modify that to use SET. However, when doing so, we get the following error:

            [0] => Google\AdsApi\AdWords\v201710\cm\RequiredError Object
                (
                    [reason:protected] => REQUIRED
                    [fieldPath:protected] => operations[0].operand.id
                    [fieldPathElements:protected] => Array
                        (
                            [0] => Google\AdsApi\AdWords\v201710\cm\FieldPathElement Object
                                (
                                    [field:protected] => operations
                                    [index:protected] => 0
                                )

                            [1] => Google\AdsApi\AdWords\v201710\cm\FieldPathElement Object
                                (
                                    [field:protected] => operand
                                    [index:protected] =>
                                )

                            [2] => Google\AdsApi\AdWords\v201710\cm\FieldPathElement Object
                                (
                                    [field:protected] => id
                                    [index:protected] =>
                                )

                        )

                    [trigger:protected] =>
                    [errorString:protected] => RequiredError.REQUIRED
                    [ApiErrorType:protected] => RequiredError


Suffice to say, we are missing an id somewhere. But how and where are we missing it? There are no examples to help us down the path. Our current code is:

function updateTextAdsCriterionOperations($expandText) {
    $ExpandedTextAdsOperations = [];
   
    foreach ($expandText as $adText) {
        // Create an expanded text ad.
        $expandedTextAd = new ExpandedTextAd();
        $expandedTextAd->setId( $adText[4] );
        $expandedTextAd->setHeadlinePart1( $adText[5] );
        $expandedTextAd->setHeadlinePart2( $adText[6] );
        $expandedTextAd->setDescription( $adText[7] );
        $expandedTextAd->setFinalUrls([ $adText[10] ]);
        $expandedTextAd->setPath1( $adText[8] );
        $expandedTextAd->setPath2( $adText[9] );
//        $expandedTextAd->setStatus( ExpandedTextAd::PAUSED );
        // Create ad group ad.
        $adGroupAd = new AdGroupAd();
        $adGroupAd->setAdGroupId( $adText[2] );
        $adGroupAd->setAd( $expandedTextAd );
        // Optional: Set additional settings.
        $adGroupAd->setStatus( AdGroupAdStatus::PAUSED );
        // Create ad group ad operation and add it to the list.
        $operation = new AdGroupAdOperation();
        $operation->setOperand( $adGroupAd );
        $operation->setOperator( Operator::SET );
        $ExpandedTextAdsOperations[] = $operation;
//        file_put_contents("batchjob.txt", print_r($operation, true), FILE_APPEND);
    }
   
    return $ExpandedTextAdsOperations;
}

function UploadSettings(AdWordsServices $adWordsServices, AdWordsSession $session, $expAdTextAds) {
     try {
        $batchJobService = $adWordsServices->get($session, BatchJobService::class);
        // Create a BatchJob.
        $addOp = new BatchJobOperation();
        $addOp->setOperator(Operator::SET);
        $addOp->setOperand(new BatchJob());
        $result = $batchJobService->mutate([$addOp]);
        $batchJob = $result->getValue()[0];
        // Get the upload URL from the new job.
        $uploadUrl = $batchJob->getUploadUrl()->getUrl();
        printf("Created BatchJob with ID %d, status '%s' and upload URL '%s'.<br>",
            $batchJob->getId(), $batchJob->getStatus(), $uploadUrl);
        // Use BatchJobs to upload all operations.
        $batchJobs = new BatchJobs($session);
       
        // Generate and upload the first set of operations.
        $adGroupCriterionOperations = updateTextAdsCriterionOperations($expAdTextAds);
        $batchJobUploadStatus = $batchJobs->uploadIncrementalBatchJobOperations(
            $adGroupCriterionOperations,
            new BatchJobUploadStatus($uploadUrl, $session)
        );
        printf("Uploaded %d operations for batch job with ID %d.<br>",
            count($adGroupCriterionOperations), $batchJob->getId());
       
        $batchJobs->closeIncrementalUpload($batchJobUploadStatus);
        // Poll for completion of the batch job using an exponential back off.
    } catch (Exception $e) {
        echo "<pre>";
        print_r($e);
        echo "</pre>";
    }
    $pollAttempts = 0;
    $isPending = true;
    $wasCancelRequested = false;
    $selector = new Selector();
    $selector->setFields(
        ['Id', 'Status', 'DownloadUrl', 'ProcessingErrors', 'ProgressStats']);
    $selector->setPredicates([
        new Predicate('Id', PredicateOperator::EQUALS, [$batchJob->getId()])
    ]);
    do {
        $sleepSeconds = POLL_FREQUENCY_SECONDS * pow(2, $pollAttempts);
        printf("Sleeping %d seconds...<br>", $sleepSeconds);
        sleep($sleepSeconds);
        $batchJob = $batchJobService->get($selector)->getEntries()[0];
        printf("Batch job ID %d has status '%s'.<br>", $batchJob->getId(),
            $batchJob->getStatus());
        $pollAttempts++;
        if ($batchJob->getStatus() !== BatchJobStatus::ACTIVE &&
            $batchJob->getStatus() !== BatchJobStatus::AWAITING_FILE &&
            $batchJob->getStatus() !== BatchJobStatus::CANCELING) {
                $isPending = false;
            }
            // Optional: Cancel the job if it has not completed after polling
            // MAX_POLL_ATTEMPTS times.
            if ($isPending && !$wasCancelRequested
                && $pollAttempts === MAX_POLL_ATTEMPTS) {
                    $batchJob->setStatus(BatchJobStatus::CANCELING);
                    $batchJobSetOperation = new BatchJobOperation();
                    $batchJobSetOperation->setOperand($batchJob);
                    $batchJobSetOperation->setOperator(Operator::SET);
                    // Only request cancellation once per job.
                    $wasCancelRequested = true;
                    try {
                        $batchJob =
                        $batchJobService->mutate([$batchJobSetOperation])->getValue()[0];
                        printf("Requested cancellation of batch job with ID %d.<br>",
                            $batchJob->getId());
                        // Reset the poll attempt counter to wait for cancellation.
                        $pollAttempts = 0;
                    } catch (ApiException $e) {
                        $errors = $e->getErrors();
                        if ($errors !== null
                            && count($errors) > 0
                            && $errors[0] instanceof BatchJobError) {
                                $batchJobError = $errors[0];
                                if ($batchJobError->getReason()
                                    === BatchJobErrorReason::INVALID_STATE_CHANGE) {
                                        printf(
                                            "Attempt to cancel batch job with ID %d was rejected because"
                                            . " the job already completed or was canceled.<br>",
                                            $batchJob->getId()
                                            );
                                        continue;
                                    }
                            }
                            throw $e;
                    } finally {
                        // Reset the poll attempt counter to wait for cancellation.
                        $pollAttempts = 0;
                    }
                }
    } while ($isPending && $pollAttempts <= MAX_POLL_ATTEMPTS);
    if ($isPending) {
        throw new UnexpectedValueException(
            sprintf('Job is still pending state after polling %d times.',
                MAX_POLL_ATTEMPTS));
    }
    if ($batchJob->getProcessingErrors() !== null) {
        $i = 0;
        foreach ($batchJob->getProcessingErrors() as $processingError) {
            printf(
                " Processing error [%d]: errorType=%s, trigger=%s, errorString=%s,"
                . " fieldPath=%s, reason=%s<br>",
                $i++,
                $processingError->getApiErrorType(),
                $processingError->getTrigger(),
                $processingError->getErrorString(),
                $processingError->getFieldPath(),
                $processingError->getReason()
                );
        }
    } else {
        printf("No processing errors found.<br>");
    }
    if ($batchJob->getDownloadUrl() !== null
        && $batchJob->getDownloadUrl()->getUrl() !== null) {
            $mutateResults = $batchJobs->downloadBatchJobResults(
                $batchJob->getDownloadUrl()->getUrl());
            printf("Downloaded results from %s:<br>",
                $batchJob->getDownloadUrl()->getUrl());
            if (count($mutateResults) === 0) {
                printf("  No results available.<br>");
            } else {
                foreach ($mutateResults as $mutateResult) {
                    $outcome = $mutateResult->getErrorList() === null ? 'SUCCESS' : 'FAILURE';
                    printf("  Operation [%d] - %s<br>", $mutateResult->getIndex(),
                        $outcome);
                    if ( $outcome == "FAILURE" ){
                        print_r( $mutateResult->getErrorList() );
                    }
                }
            }
        } else {
            printf("No results available for download.<br>");
        }
}

So we have "UploadSettings" called and we get the error somewhere right after or at " $result = $batchJobService->mutate([$addOp]);". The thing is, the code never even seems to get around to adding the expanded text ad parameters. Can anyone help out with what we're exactly missing?

Sreelakshmi Sasidharan (AdWords API Team)

unread,
Dec 18, 2017, 2:48:53 PM12/18/17
to AdWords API Forum
Hi, 

Ad objects are immutable in AdWords. To modify an Ad, you will need to submit a REMOVE operation with the existing Ad and an ADD operation for a new Ad with the modified attributes. This creates a new Ad ID, so stats for the original Ad and the new Ad will appear under separate IDs in reports. Please check here for more details. Could you please try this and let me know if you are still facing issues?

Thanks,
Sreelakshmi, AdWords API Team

Prototype.co

unread,
Dec 19, 2017, 4:41:46 AM12/19/17
to AdWords API Forum
Hi,

thanks for the quick answer. Indeed, you are right, but unfortunately, we are still facing the same problem. However, as I understand, there is one thing we can change with Ads, and that is their Status. The problem is that our code, which looks like the following:

function UploadSettings(AdWordsServices $adWordsServices, AdWordsSession $session, $expAdTextAds) {
     
try {
        $batchJobService
= $adWordsServices->get($
session
, BatchJobService::class);
       
// Create a BatchJob.
        $addOp
= new BatchJobOperation();
        $addOp
->setOperator(Operator::SET);
        $addOp
->setOperand(new BatchJob());

        $result
= $batchJobService->mutate([$addOp]); //<== THIS IS WHERE THE CODE DIES

        $batchJob
= $result->getValue()[0];
       
// Get the upload URL from the new job.
        $uploadUrl
= $batchJob->getUploadUrl()->getUrl();
        printf
("Created BatchJob with ID %d, status '%s' and upload URL '%s'.<br>",
            $batchJob
->getId(), $batchJob->getStatus(), $uploadUrl);
       
// Use BatchJobs to upload all operations.
        $batchJobs
= new BatchJobs($session);
       
       
// Generate and upload the first set of operations.

        $adGroupCriterionOperations
= updateTextAdsCriterionOperations($expAdTextAds); //<== THIS IS WHERE THE OPERATIONS WOULD BE ADDED. THE CODE NEVER EVEN MAKES IT TO THIS POINT.

        $batchJobUploadStatus
= $batchJobs->uploadIncrementalBatchJobOperations(
            $adGroupCriterionOperations
,
           
new BatchJobUploadStatus($uploadUrl, $session)
       
);
        printf
("Uploaded %d operations for batch job with ID %d.<br>",
            count
($adGroupCriterionOperations), $batchJob->getId());
       
        $batchJobs
->closeIncrementalUpload($batchJobUploadStatus);
       
// Poll for completion of the batch job using an exponential back off.
   
}


dies at the $result line. Please see my post-line comments - the code never even gets to the point where it would receive the specific operations. Now, I know that the operation here is 'SET' and not 'REMOVE', but it would suit us a lot better if we did not remove the old Ad, just set it to paused, and create a new one to replace it.
Also and most importantly, we are facing the same issue not just with Expanded Text Ads, but also with Ad Groups - if we try to modify their status with BatchJobs, the code dies off this exact same way. Due to this, we are now forced to use simple mutate operations, but we really want to replace them with proper BatchJobs to save some on our call count.

So to sum things up, can you point us in the right direction as to why the code above is dying on us, even for Ad Group status updates, or Expanded Text Ad status updates as well?

Thanks,
Balazs


Sreelakshmi Sasidharan (AdWords API Team)

unread,
Dec 19, 2017, 2:26:01 PM12/19/17
to AdWords API Forum
Hi Balazs, 

You will first need to create a batch job and then set the operations to that batch job. In your code snippet I see that you are performing a SET operator on the BatchJobService. This means you want to modify the batch job itself. For instance, cancel the Batch job. You can refer to this code sample in PHP where a complete campaign is being added via Batch job. You can remove the code corresponding to all other operations and just keep the one corresponding to the AdGroupCriterionService or AdGroupAdService. In the logic for buildAdGroupCriterionOperations function, you can update the operator to SET instead of ADD. Could you give this a try and let me know if that works for you?

Prototype.co

unread,
Dec 20, 2017, 5:59:24 AM12/20/17
to AdWords API Forum
Hi,

Once again, thanks for your quick answer. Everything seems to work now, except for one small part. How could we change the status of a pre-existing Expanded Text Ad from Enabled to Paused? I believe this would require a SET operator, but if we try to use SET for an expanded text ad, we always get an error stating Ads are immutable (I'm not sure, but maybe it was CANNOT_USE_AD_SUBCLASS_FOR_OPERATOR?). We can remove the ads now, but for most cases, setting them to Paused would suit us a lot better. All the information we pass the operation are the required IDs and the Status.

Do you have any ideas on how could we set the status of Expanded Text Ads to 'Paused' using a BatchJob?

Thanks,
Balazs

Sreelakshmi Sasidharan (AdWords API Team)

unread,
Dec 20, 2017, 2:00:45 PM12/20/17
to AdWords API Forum
Hi Balazs, 

You will have to set just the AdId and the status in the Ad object while modifying the status of an expanded text ad. Please check the PauseAd sample in PHP for reference. 
Reply all
Reply to author
Forward
0 new messages