google-ads-api icon indicating copy to clipboard operation
google-ads-api copied to clipboard

"null" is a valid value for some update properties

Open ellisonc opened this issue 2 years ago • 1 comments

According to the Google Documentation, to set a keyword's cpc bid to the default (ad group level or campaign level) you should make an update call with the cpc_bid_micros set to null. See Removing Bids. This doesn't seem to be possible using this library.

When I run an update operation on adgroupcriterion with the following structure:

customer.adGroupCriteria.update({
  resource_name: 'customers/customerid/adGroupCriterion/resourceid,
  cpc_bid_micros: null
}, {partial_failure:true})

The library generates the following FieldMask which is missing cpc_bid_micros

update_mask: {
  paths: ["resource_name"]
}

For anyone looking for a quick workaround, here's a callback using the undocumented onMutationStart hook which can be placed in the Customer creation:

let customer = Customer({
        customer_id: "",
        login_customer_id: "",
        refresh_token: ""
      },
        {
            onMutationStart(args) {
              //TODO this hook is a workaround for a bug in the library that leaves the cpc_bid_micros out of the
              // update mask when the value is null.  
              if(args['mutation'] && args['mutation']['operations']) {
                args['mutation']['operations'].forEach(op => {
                  if(Object.keys(op['update']).includes('cpc_bid_micros') && !op['update_mask']['paths'].includes('cpc_bid_micros')) {
                    op['update_mask']['paths'].push('cpc_bid_micros');
                  }
                })
              }
          }});

ellisonc avatar Jan 12 '22 20:01 ellisonc

Faced with the same problem with clearing campaign.target_spend.cpc_bid_ceiling_micros and campaign.maximize_conversions.target_cpa. Problem is global for this lib. If you set null to updating value, lib just skip and not set it as value to update. I've updated code provided by @ellisonc above to make it work with all null values and send them to Google. Works good for me:

type DataObject = {[key: string]: any};

/**
 * Function to make object flatten. Can be used https://www.npmjs.com/package/flat package instead
 */
export function flatObject(input: DataObject): DataObject {
  function flat(res: DataObject, key: string, val: any, pre = ''): DataObject {
    const prefix = [pre, key].filter(v => v).join('.');
    return typeof val === 'object' && val !== null
      ? Object.keys(val).reduce((prev, curr) => flat(prev, curr, val[curr], prefix), res)
      : Object.assign(res, { [prefix]: val});
  }

  return Object.keys(input).reduce((prev, curr) => flat(prev, curr, input[curr]), {});
}

const customer = Customer({
  customer_id: "",
  login_customer_id: "",
  refresh_token: ""
}, {
  onMutationStart: (args: any) => {
    const mutation = args.mutation as {operations: ({update: DataObject, update_mask: IFieldMask})[]}
    if (!mutation?.operations?.length) {
      return;
    }
    mutation?.operations.forEach((op) => {
      const flatUpdateObj = flatObject(op.update);
      const nullablePaths = Object.keys(flatUpdateObj).filter((flatKey) => flatUpdateObj[flatKey] === null);
      op.update_mask.paths = op.update_mask.paths.concat(nullablePaths);
    });
  }
});

Valzon avatar Apr 13 '22 14:04 Valzon