Skip to main content

Victor Serban

Last updated: 14/11/2024

Tweaked the Disapproved Products Alerts script by Nils Rooijmans as it wasn’t working for me last time I ran it with a few tweaks that reports on the total products disapproved in the Shopping and anything that had at least one click in the last 90 days.


// SETTINGS
var SPREADSHEET_URL = ""; // sheets.new
var GMC_ID = "";  
var INCLUDE_LOCAL_PRODUCTS = false;  
var RECIPIENT_EMAIL = "";  
var MAX_RESULTS_PER_PAGE = 250;

// REPORT HEADERS
var SHEET_REPORT_HEADER = [
    "Product ID",
    "Product Title",
    "Product Link",
    "Shopping Destination Status",
    "Disapproval Reasons",
    "Policy Link",
    "Clicks"
];

// MAIN FUNCTION
function main() {
    var allDisapprovedProducts = fetchDisapprovedProducts();
    var disapprovedWithClicks = addClickDataFromGoogleAds(allDisapprovedProducts);

    if (disapprovedWithClicks.length > 0) {
        writeReport(disapprovedWithClicks);
        sendEmailNotification(allDisapprovedProducts.length, disapprovedWithClicks.length);
    } else {
        sendEmailNotification(allDisapprovedProducts.length, 0);
    }

    Logger.log(`Script completed successfully. Total disapproved products: ${allDisapprovedProducts.length}, with clicks > 0: ${disapprovedWithClicks.length}`);
}

// FETCH DISAPPROVED PRODUCTS
function fetchDisapprovedProducts() {
    var disapprovedProducts = [];
    var pageToken;

    Logger.log("Fetching disapproved products from Google Merchant Center...");

    do {
        var productStatuses = ShoppingContent.Productstatuses.list(GMC_ID, {pageToken: pageToken, maxResults: MAX_RESULTS_PER_PAGE});
        
        if (productStatuses.resources) {
            productStatuses.resources.forEach(function(product) {
                var productId = product.productId.split(':').pop().toLowerCase();
                if (INCLUDE_LOCAL_PRODUCTS || !product.productId.startsWith("local:")) {
                    var isDisapproved = false;
                    var reasons = new Set();
                    var policyLink = null;

                    product.destinationStatuses.forEach(function(status) {
                        if (status.destination === "Shopping" && status.status === "disapproved") {
                            isDisapproved = true;
                            (product.itemLevelIssues || []).forEach(function(issue) {
                                if (issue.servability === "disapproved") {
                                    reasons.add(issue.description);
                                    if (!policyLink && issue.documentation) {
                                        policyLink = issue.documentation;
                                    }
                                }
                            });
                        }
                    });

                    if (isDisapproved) {
                        disapprovedProducts.push({
                            productId: productId,
                            title: product.title || "No title",
                            link: product.link || "No link available",
                            shoppingStatus: "disapproved",
                            reasons: Array.from(reasons).join("; "),
                            policyLink: policyLink || "No policy link provided",
                            clicks: 0
                        });
                    }
                }
            });
        }
        pageToken = productStatuses.nextPageToken;
    } while (pageToken);

    Logger.log("Total disapproved products retrieved from GMC: " + disapprovedProducts.length);
    return disapprovedProducts;
}

// ADD CLICK DATA AND FILTER PRODUCTS WITH CLICKS > 0
function addClickDataFromGoogleAds(disapprovedProducts) {
    Logger.log("Fetching click data from Google Ads for products with clicks > 0...");
    var productClickMap = {};
    var dateRange = getLast90DaysDateRange();

    var gaqlQuery = `
        SELECT segments.product_item_id, metrics.clicks 
        FROM shopping_performance_view 
        WHERE segments.product_item_id IN (${disapprovedProducts.map(p => `'${p.productId}'`).join(", ")})
        AND segments.date BETWEEN '${dateRange.startDate}' AND '${dateRange.endDate}'
        AND metrics.clicks > 0
    `;
    var results = AdsApp.search(gaqlQuery);

    while (results.hasNext()) {
        var result = results.next();
        productClickMap[result.segments.productItemId] = result.metrics.clicks || 0;
    }

    // Only keep products with clicks > 0 and assign click values
    disapprovedProducts = disapprovedProducts.filter(function(product) {
        if (productClickMap[product.productId] > 0) {
            product.clicks = productClickMap[product.productId];
            return true;
        }
        return false;
    });

    // Sort products by clicks in descending order
    disapprovedProducts.sort((a, b) => b.clicks - a.clicks);

    Logger.log("Total disapproved products with clicks > 0: " + disapprovedProducts.length);
    return disapprovedProducts;
}

// WRITE REPORT TO GOOGLE SHEETS
function writeReport(data) {
    var ss = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
    var sheet = ss.getActiveSheet();
    sheet.clear();
    sheet.appendRow(SHEET_REPORT_HEADER);

    var formattedData = data.map(function(item) {
        return [
            item.productId,
            item.title,
            item.link,
            item.shoppingStatus,
            item.reasons,
            item.policyLink,
            item.clicks
        ];
    });
    sheet.getRange(2, 1, formattedData.length, SHEET_REPORT_HEADER.length).setValues(formattedData);

    Logger.log("Report successfully written to Google Sheets.");
}

// HELPER FUNCTION TO GET LAST 90 DAYS DATE RANGE
function getLast90DaysDateRange() {
    var today = new Date();
    var endDate = today.toISOString().slice(0, 10);
    var startDate = new Date(today);
    startDate.setDate(today.getDate() - 90);
    return { startDate: startDate.toISOString().slice(0, 10), endDate: endDate };
}

// SEND EMAIL NOTIFICATION WITH TOTAL AND CLICKS > 0 DISAPPROVALS
function sendEmailNotification(totalDisapprovals, disapprovalsWithClicks) {
    var accountName = AdsApp.currentAccount().getName(); 
    var subject = `GMC Disapproval Alert - ${accountName}`;
    var body = `${totalDisapprovals} total disapproved products found in Google Merchant Center, with ${disapprovalsWithClicks} disapproved products having clicks > 0.\nView report: ${SPREADSHEET_URL}\n\nGMC Disapproved Products Alert - victorserban.com`;

    MailApp.sendEmail(RECIPIENT_EMAIL, subject, body);
}

Leave a Reply

Your email address will not be published. Required fields are marked *