google-ads-api
Version:
Google Ads API Client Library for Node.js
107 lines (106 loc) • 4.31 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ParsingError = void 0;
exports.parse = parse;
exports.getGAQLFields = getGAQLFields;
exports.getReportOptionFields = getReportOptionFields;
exports.parseRows = parseRows;
const long_1 = __importDefault(require("long"));
const protos_1 = require("./protos");
const utils_1 = require("./utils");
exports.ParsingError = {
NO_REPORT_OPTIONS_OR_GAQL_QUERY: "Must provided reportOptions or gaqlString to parse results.",
NO_FIELDS_IN_GAQL_QUERY: "GAQL Query must contain at least one attribute, metric or segment.",
NO_FIELDS_IN_REPORT_OPTIONS: "Report Options must contain at least one attribute, metric or segment.",
};
/**
@description Parse the results of a query
@example
const parsedResults = parse({ results, reportOptions })
const parsedResults = parse({ results, gaqlString })
*/
function parse({ results, reportOptions, gaqlString, }) {
if (results.length === 0) {
return results;
}
if (typeof reportOptions === "undefined" &&
typeof gaqlString === "undefined") {
throw new Error(exports.ParsingError.NO_REPORT_OPTIONS_OR_GAQL_QUERY);
}
const queryFields = reportOptions
? getReportOptionFields(reportOptions)
: getGAQLFields(gaqlString);
// Add in all relevant resource_name fields, which are always returned by API
const entities = queryFields.map((field) => field.split(".")[0]);
const resourceNameFields = protos_1.fields.resourceNames.filter((resourceNameField) => entities.includes(resourceNameField.split(".")[0]));
const allFields = [...queryFields, ...resourceNameFields];
return parseRows(results, allFields);
}
// This function assumes that a gaql query is of the format "select * * * from * ...".
// Queries that are not in this format should have thrown an error when called.
function getGAQLFields(gaqlString) {
const normalisedQuery = (0, utils_1.normaliseQuery)(gaqlString);
const fields = normalisedQuery
.toLowerCase()
.replace(/(^\s*select)|( from .*)|(\s+)/g, "")
.split(",")
.filter((field) => field.length > 0);
if (!fields.length) {
throw new Error(exports.ParsingError.NO_FIELDS_IN_GAQL_QUERY);
}
return fields;
}
function getReportOptionFields(reportOptions) {
const fields = [
...(reportOptions.attributes || []),
...(reportOptions.metrics || []),
...(reportOptions.segments || []),
];
if (!fields.length) {
throw new Error(exports.ParsingError.NO_FIELDS_IN_REPORT_OPTIONS);
}
return fields;
}
function parseRows(rows, fields) {
const fieldsPreSplit = {};
// pre-split all the field strings for performance reasons (increases speed by ~5x for large number of rows)
for (const field of fields) {
fieldsPreSplit[field] = field.split(".");
}
const newRows = [];
for (let r = 0; r < rows.length; r++) {
const newRow = {};
const originalRow = protos_1.services.GoogleAdsRow.fromObject(rows[r]);
for (const split in fieldsPreSplit) {
// @ts-expect-error These are the best we can do for these types
const [parent, ...children] = fieldsPreSplit[split];
// Ignore null fields (unspecified resource names)
if (!originalRow[parent]) {
continue;
}
newRow[parent] = parseNestedValues(newRow[parent], originalRow[parent], parent, children);
}
newRows.push(newRow);
}
return newRows;
}
function parseNestedValues(row, data, field, paths) {
if (!data)
return null;
const [parentField, ...childFields] = paths;
if (!row)
row = {};
if (childFields.length === 0) {
const rawVal = data[parentField];
const parsedVal = long_1.default.isLong(rawVal)
? new long_1.default(rawVal.low, rawVal.high, rawVal.unsigned).toNumber()
: rawVal;
row[parentField] = parsedVal;
return row;
}
row[parentField] = parseNestedValues(row[parentField], data[parentField], parentField, childFields);
return row;
}