Sklik skript pro PNO report účtů z různých pohledů
Sklik skript slouží jako report návratnosti Vašeho Sklik účtu
Funkce skriptu
- Skript vypisuje výkon Vašeho účtu a klíčové metriky z různých pohledů:
- Po dnech.
- Po týdnech.
- Po měsicích.
- Pro každou kampaň za zvolené období.
- Pro každou sestavu za zvolené období.
- Skript je i skvělý jako klientský report.
- Skript hlídá, když na účtu dojde kredit, v takovém případě posílá email s upozorněním na email vyplnění v nastavení.
- Skript počítá na základě Vámi vyplněné průměrné marže zisk a ztrátu účtu. Je chopný Vás upozornit v případě, že za včerejší den byla ztráta účtu.
Zdrojový kód skriptu
/** ID of spreadsheet to use. Fill-in either ID or URL. */
var SPREADSHEET_URL = "";
/** URL of spreadsheet to use. Fill-in either ID or URL. */
var SPREADSHEET_ID = null;
/** Account to analyze. */
var ACCOUNT_USERNAME = '';
/** Set to true if you want to send emails. false is useful for testing. */
var SEND_EMAILS = true;
/**
* Script entry-point.
*/
function main() {
// Open the spreadsheet.
var reportSpreadsheet;
if (SPREADSHEET_URL) {
reportSpreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
}
else {
reportSpreadsheet = SpreadsheetApp.openById(SPREADSHEET_ID);
}
var sklik = SklikAPI.Connect();
sklik.loginFromProperties();
if (ACCOUNT_USERNAME) {
sklik.setUserIdByUsername(ACCOUNT_USERNAME);
}
// Set up spreadsheet locale to english - numbers format.
reportSpreadsheet.setSpreadsheetLocale('en');
reportSpreadsheet.setSpreadsheetTimeZone("Europe/Prague");
var sheetSettings = reportSpreadsheet.getSheetByName("Nastavení");
if (sheetSettings == null) {
reportSpreadsheet.insertSheet("Nastavení", 0);
var sheetSettings = reportSpreadsheet.getSheetByName("Nastavení");
sheetSettings.getRange(1, 1, 6, 3).setValues([
["Nastavení skriptu, vložte do buňky vedle daná data", "Vaše nastavení", "Příklad"],
["Vaše marže v %", 30, 30], ["Při jakém denní ztrátě chcete být upozornění", 0, 500],
["Váš email pro upozornění", "jakub.kasparu@lynt.cz", "jakub.kasparu@lynt.cz"],
["Předmět emailu", "", "Název účtu - včera vznikla ztráta"],
["Kolik dní zpátky koukat na kampaně a sestavy", 150, 150]]);
sheetSettings.setColumnWidth(1, 330);
sheetSettings.setColumnWidth(2, 200);
sheetSettings.setColumnWidth(3, 200);
sheetSettings.getRange(1, 1, 6, 3).setBorder(true, true, true, true, true, true);
}
// Delete the 'List 1' sheet, which is created by default in english locale.
var sheetList1 = reportSpreadsheet.getSheetByName("List 1");
if (sheetList1 != null) {
reportSpreadsheet.deleteSheet(sheetList1);
}
// Delete the "Sheet 1" sheet, which is created by default in english locale.
var sheetSheet1 = reportSpreadsheet.getSheetByName("Sheet1");
if (reportSpreadsheet.getSheetByName("Sheet1") != null) {
reportSpreadsheet.deleteSheet(sheetSheet1);
}
var allCampaignIds = [];
var allCampaigns = sklik.Campaigns.list({
includeDeleted: true
}).campaigns;
for (var i_1 = 0; i_1 < allCampaigns.length; i_1++) {
allCampaignIds.push(allCampaigns[i_1].id);
}
var sheetDailyAccountOverview = lynt_create_or_get_sheet(reportSpreadsheet, "Denní výkon účtu", 1);
var sheetWeeklyAccountOverview = lynt_create_or_get_sheet(reportSpreadsheet, "Týdenní výkon účtu", 2);
var sheetMonthlyOverview = lynt_create_or_get_sheet(reportSpreadsheet, "Měsíční výkon účtu", 3);
var campaingsPerformance = lynt_create_or_get_sheet(reportSpreadsheet, "Výkon kampaní", 4);
var adGroupPerformance = lynt_create_or_get_sheet(reportSpreadsheet, "Historický výkon sestav", 5);
// Get settings.
var profit = sheetSettings.getRange("B2").getValue() / 100;
var daysToAnalyze = sheetSettings.getRange("B6").getValue();
createDailyReport(sklik, sheetDailyAccountOverview, allCampaignIds, profit);
createWeeklyReport(sklik, sheetWeeklyAccountOverview, allCampaignIds, profit);
createMonthlyReport(sklik, sheetMonthlyOverview, allCampaignIds, profit);
var campaigns = {};
var campaignIds = [];
var campaignList = sklik.Campaigns.list().campaigns;
for (var i = 0; i < campaignList.length; i++) {
var campaign = campaignList[i];
if (campaignList[i].status == 'active') {
campaignIds.push(campaign.id);
campaigns[campaign.id] = {
id: campaign.id,
name: campaign.name
};
}
}
createCampaignReport(sklik, campaingsPerformance, campaignIds, campaigns, profit, daysToAnalyze);
createGroupReport(sklik, adGroupPerformance, campaignIds, campaigns, profit, daysToAnalyze);
sklik.Client.logout();
if (SEND_EMAILS) {
//If there is bigger loss than your settings, scripts will send you email with alert
var alertEmail = sheetSettings.getRange("B4").getValue();
var lastRow = sheetDailyAccountOverview.getLastRow();
var lastCol = sheetDailyAccountOverview.getLastColumn();
var user = Session.getEffectiveUser();
var replyTo = user.getEmail();
var yesterdayProfitLoss = sheetDailyAccountOverview.getRange(lastRow, lastCol).getValue();
if (yesterdayProfitLoss <= sheetSettings.getRange("B3").getValue()) {
MailApp.sendEmail(alertEmail, replyTo, sheetSettings.getRange("B5").getValue(), "Včera jsme zaznamenali ztrátu na Vašem účtě ve výši " + yesterdayProfitLoss + " Kč");
}
//If your account daily spend = 0, you will get alert email
if (sheetDailyAccountOverview.getRange(lastRow, 2).getValue() == 0) {
MailApp.sendEmail(alertEmail, replyTo, sheetSettings.getRange("B5").getValue() + " Neaktivní kampaně", "Na Vašem Sklik účtu není kredit");
}
}
}
var ReportAggregation = (function() {
function ReportAggregation() {}
ReportAggregation.prototype.getCpc = function() {
return this.clicks > 0 ? Math.round(this.price / this.clicks) : 0;
};
ReportAggregation.prototype.getCtr = function() {
return this.impressions > 0 ? Math.round(this.clicks / this.impressions * 10000) / 100 : 0;
};
ReportAggregation.prototype.getAvgPosition = function() {
return this.impressions > 0 ? Math.round(this.avgPosition / this.impressions * 100) / 100 : 0;
};
ReportAggregation.prototype.getAvgConversionPrice = function() {
return this.conversions > 0 ? Math.round(this.price / this.conversions) : 0;
};
return ReportAggregation;
})();
/**
* Creates a report for last 60 days.
*/
function createDailyReport(sklik, sheet, allCampaignIds, profit) {
var dateFrom = lynt_get_date(60);
var dateTo = lynt_get_date(1);
/*var accountStats = sklik.Client.stats({
dateFrom: dateFrom,
dateTo: dateTo,
granularity: "daily"
}).report;*/
var accountStats = aggregateStatistics(sklik, allCampaignIds, dateFrom, dateTo, "daily");
// Create table header.
var dailyData = [["Datum", "Náklady", "Počet zobrazení", "Prokliky", "Průměrné CPC", "CTR", "Prům. Pozice", "Konverze", "Cena konverze", "Celk. hodnota konverzí", "Průměrná objednávka", "PNO", "Zisk/Ztráta"]];
// Prepare data for spreadsheets.
for (var i = 0; i < accountStats.length; i++) {
/*var reportRow = accountStats[i];
dailyData.push([
reportRow.date,
reportRow.price / 100,
reportRow.clicks, // SKLIK_FAIL: should be reportRow.impressions
reportRow.clicks, // SKLIK_FAIL: ? number of clicks not found in API
reportRow.cpc, // SKLIK_FAIL: always 0
reportRow.ctr, // SKLIK_FAIL: incorrect value
reportRow.conversions / 100, // SKLIK_FALI: should be reportRow.avgPosition
reportRow.transactions,
reportRow.transactionAvgPrice / 100,
reportRow.transactionAvgValue * reportRow.transactions, // SKLIK_FAIL: ? number of clicks not found in API
0,
0,
0
]);*/
var reportRow = accountStats[i];
dailyData.push([
reportRow.date,
reportRow.price / 100,
reportRow.impressions,
reportRow.clicks,
reportRow.getCpc() / 100,
reportRow.getCtr(),
reportRow.getAvgPosition(),
reportRow.conversions,
reportRow.getAvgConversionPrice() / 100,
reportRow.conversionValue / 100,
0,
0,
0
]);
}
setData(sheet, dailyData, profit);
}
/**
* Creates a report for last 20 weeks.
*/
function createWeeklyReport(sklik, sheet, allCampaignIds, profit) {
var dateFrom = lynt_get_xdays_back_from_sunday(20 * 7 - 1); // 20 weeks
var dateTo = lynt_get_suday();
var accountStats = aggregateStatistics(sklik, allCampaignIds, dateFrom, dateTo, "weekly");
// Create table header.
var weeklyData = [["Datum", "Náklady", "Počet zobrazení", "Prokliky", "Průměrné CPC", "CTR", "Prům. Pozice", "Konverze", "Cena konverze", "Celk. hodnota konverzí", "Průměrná objednávka", "PNO", "Zisk/Ztráta"]];
// Prepare data for spreadsheets.
for (var i = 0; i < accountStats.length; i++) {
var reportRow = accountStats[i];
weeklyData.push([
lynt_date_add(reportRow.date, -7 * 86400 * 1000),
reportRow.price / 100,
reportRow.impressions,
reportRow.clicks,
reportRow.getCpc() / 100,
reportRow.getCtr(),
reportRow.getAvgPosition(),
reportRow.conversions,
reportRow.getAvgConversionPrice() / 100,
reportRow.conversionValue / 100,
0,
0,
0
]);
}
setData(sheet, weeklyData, profit);
}
/**
* Creates a report for last 12 months
*/
function createMonthlyReport(sklik, sheet, allCampaignIds, profit) {
var dateFrom = lynt_get_date(365);
var dateTo = lynt_get_date(1);
var accountStats = aggregateStatistics(sklik, allCampaignIds, dateFrom, dateTo, "monthly");
// Create table header.
var monthlyData = [["Datum", "Náklady", "Počet zobrazení", "Prokliky", "Průměrné CPC", "CTR", "Prům. Pozice", "Konverze", "Cena konverze", "Celk. hodnota konverzí", "Průměrná objednávka", "PNO", "Zisk/Ztráta"]];
// Prepare data for spreadsheets.
sheet.clearContents();
for (var i = 0; i < accountStats.length; i++) {
var reportRow = accountStats[i];
monthlyData.push([
reportRow.date,
reportRow.price / 100,
reportRow.impressions,
reportRow.clicks,
reportRow.getCpc() / 100,
reportRow.getCtr(),
reportRow.getAvgPosition(),
reportRow.conversions,
reportRow.getAvgConversionPrice() / 100,
reportRow.conversionValue / 100,
0,
0,
0
]);
}
var range = sheet.getRange(1, 1, monthlyData.length, monthlyData[0].length);
range.setValues(monthlyData);
setData(sheet, monthlyData, profit);
}
/**
* Creates a per-campaign report.
*/
function createCampaignReport(sklik, sheet, campaignIds, campaigns, profit, daysToAnalyze) {
var dateFrom = lynt_get_date(daysToAnalyze);
var dateTo = lynt_get_date(1);
// Create table header.
var campaignData = [["Kampaň", "Náklady", "Počet zobrazení", "Prokliky", "Průměrné CPC", "CTR", "Prům. Pozice", "Konverze", "Cena konverze", "Celk. hodnota konverzí", "Průměrná objednávka", "PNO", "Zisk/Ztráta"]];
// Prepare data for spreadsheets.
var batchSize = 100;
for (var offset = 0; offset < campaignIds.length; offset += batchSize) {
var campaignStats = sklik.Campaigns.stats(campaignIds.slice(offset, offset + batchSize), {
dateFrom: dateFrom,
dateTo: dateTo,
granularity: 'total'
}).report;
for (var i = 0; i < campaignStats.length; i++) {
var campaign = campaigns[campaignStats[i].campaignId];
var reportRow = campaignStats[i].stats[0];
campaignData.push([
campaign.name,
reportRow.price / 100,
reportRow.impressions,
reportRow.clicks,
reportRow.cpc / 100,
reportRow.ctr,
reportRow.avgPosition,
reportRow.conversions,
reportRow.conversionAvgPrice / 100,
reportRow.conversionValue / 100,
0,
0,
0
]);
}
}
setData(sheet, campaignData, profit, 2, SortOrder.DESC);
}
/**
* Creates a per-ad-group report.
*/
function createGroupReport(sklik, sheet, campaignIds, campaigns, profit, daysToAnalyze) {
var dateFrom = lynt_get_date(daysToAnalyze);
var dateTo = lynt_get_date(1);
var groupIds = [];
var groups = {};
var batchSize = 100;
for (var offset = 0; offset < campaignIds.length; offset += batchSize) {
var groupsBatch = sklik.Groups.list({
campaignIds: campaignIds.slice(offset, offset + batchSize)
}).groups;
for (var i = 0; i < groupsBatch.length; i++) {
var group = groupsBatch[i];
groupIds.push(group.id);
groups[group.id] = {
id: group.id,
name: group.name,
campaignId: group.campaign.id
};
}
}
// Create table header.
var groupsData = [["Kampaň", "Sestava", "Náklady", "Počet zobrazení", "Prokliky", "Průměrné CPC", "CTR", "Prům. Pozice", "Konverze", "Cena konverze", "Celk. hodnota konverzí", "Průměrná objednávka", "PNO", "Zisk/Ztráta"]];
// Prepare data for spreadsheets.
for (var offset = 0; offset < campaignIds.length; offset += batchSize) {
var groupStats = sklik.Groups.stats(groupIds.slice(offset, offset + batchSize), {
dateFrom: dateFrom,
dateTo: dateTo,
granularity: 'total'
}).report;
for (var i = 0; i < groupStats.length; i++) {
var group = groups[groupStats[i].groupId];
var campaign = campaigns[group.campaignId];
var reportRow = groupStats[i].stats[0];
groupsData.push([
campaign.name,
group.name,
reportRow.price / 100,
reportRow.impressions,
reportRow.clicks,
reportRow.cpc / 100,
reportRow.ctr,
reportRow.avgPosition,
reportRow.conversions,
reportRow.conversionAvgPrice / 100,
reportRow.conversionValue / 100,
0,
0,
0
]);
}
}
setData(sheet, groupsData, profit, 3, SortOrder.DESC);
}
/**
* Aggregates statistics. Used to compute account-wide statistics from statistics of each campaign.
*
* Sklik API returns invalid values for account statistics.
*/
function aggregateStatistics(sklik, allCampaignIds, dateFrom, dateTo, granularity) {
var batchSize = 100;
var timeIndex = {};
var accountStats = [];
var time;
var aggregate;
for (var offset = 0; offset < allCampaignIds.length; offset += batchSize) {
var campaignStats = sklik.Campaigns.stats(allCampaignIds.slice(offset, offset + batchSize), {
dateFrom: dateFrom,
dateTo: dateTo,
granularity: granularity
}).report;
for (var i = 0; i < campaignStats.length; i++) {
for (var j = 0; j < campaignStats[i].stats.length; j++) {
var stats = campaignStats[i].stats[j];
time = stats.date.getTime();
if (timeIndex[time]) {
aggregate = timeIndex[time];
aggregate.price += stats.price;
aggregate.impressions += stats.impressions;
aggregate.clicks += stats.clicks;
aggregate.conversions += stats.conversions;
aggregate.conversionValue += stats.conversionValue;
aggregate.avgPosition += stats.impressions * stats.avgPosition;
}
else {
aggregate = new ReportAggregation();
aggregate.date = stats.date;
aggregate.price = stats.price;
aggregate.impressions = stats.impressions;
aggregate.clicks = stats.clicks;
aggregate.conversions = stats.conversions;
aggregate.conversionValue = stats.conversionValue;
aggregate.avgPosition = stats.impressions * stats.avgPosition;
accountStats.push(aggregate);
timeIndex[time] = aggregate;
}
}
}
}
return accountStats;
}
/**
* Writes data to sheet and formats the report.
*/
function setData(sheet, data, profit, sortColumn, sortOrder) {
if (sortColumn === void 0) {
sortColumn = 1;
}
if (sortOrder === void 0) {
sortOrder = SortOrder.ASC;
}
sheet.clear();
var lastEmptyRow = sheet.getMaxRows();
if (lastEmptyRow > 2) {
sheet.deleteRows(3, lastEmptyRow - 2);
}
sheet.insertRows(1, data.length);
var range = sheet.getRange(1, 1, data.length, data[0].length);
range.setValues(data);
formatAndCalculateReport(sheet, profit, sortColumn, sortOrder);
}
/**
* Formats the report.
*/
function formatAndCalculateReport(sheet, profit, sortColumn, sortOrder) {
if (sortColumn === void 0) {
sortColumn = 1;
}
if (sortOrder === void 0) {
sortOrder = SortOrder.ASC;
}
sheet.setFrozenRows(1);
sheet.sort(sortColumn, (sortOrder == SortOrder.ASC));
var lastRow = sheet.getLastRow();
var lastCol = sheet.getLastColumn();
// Format the table header.
var headerColumns = sheet.getRange(1, 1, 1, 13);
headerColumns.setFontWeight("bold");
//Creating new column with avg order value
var avgOrder = sheet.getRange(2, lastCol - 2, lastRow - 1, 1);
avgOrder.setFormulaR1C1("=IF(R[0]C[-3]=0,,R[0]C[-1]/R[0]C[-3])");
//Creating new column with PNO with Google sheet script
var pnoRange = sheet.getRange(2, lastCol - 1, lastRow - 1, 1);
pnoRange.setFormulaR1C1("=IF(R[0]C[-2]=0,,R[0]C[-10]/(R[0]C[-2]/100))");
//Creating new column with profit values based on spreadsheet formula
var profitRange = sheet.getRange(2, lastCol, lastRow - 1, 1);
profitRange.setFormulaR1C1("=R[0]C[-3]*'Nastavení'!R2C2/100-R[0]C[-11]");
var resultRange = sheet.getRange(2, lastCol - 1, lastRow - 1, 2);
var resultValues = resultRange.getValues();
// Highlight the values.
var colorProfit, colorPno;
var backgrounds = [];
for (var i = 0; i < resultValues.length; i++) {
var pnoForConditional = resultValues[i][0];
var profitLoss = resultValues[i][1];
if (pnoForConditional >= profit * 100) {
colorPno = "#FFC2B2";
}
else {
colorPno = "#CCF5CC";
}
if (profitLoss <= 0) {
colorProfit = "#FFC2B2";
}
else {
colorProfit = "#CCF5CC";
}
backgrounds.push([colorPno, colorProfit]);
}
resultRange.setBackgrounds(backgrounds);
}
/**
* Gets the day specified number of days before today.
*
* This function correctly transforms between normal time and DST.
*/
function lynt_get_date(daysBack) {
var past = new Date();
past.setHours(0);
past.setMinutes(0);
past.setSeconds(0);
past.setMilliseconds(0);
var originalOffset = past.getTimezoneOffset();
var pastTime = past.getTime() - daysBack * 86400 * 1000;
past.setTime(pastTime);
var newOffset = past.getTimezoneOffset();
var offsetDiff = newOffset - originalOffset;
if (offsetDiff != 0) {
pastTime += offsetDiff * 60 * 1000;
past.setTime(pastTime);
}
return past;
}
/**
* Gets the last sunday.
*/
function lynt_get_suday() {
var dayOffset = new Date().getDay();
return lynt_get_date(dayOffset);
}
/**
* Gets the date specified number of days before the last sunday.
*/
function lynt_get_xdays_back_from_sunday(pocet_dni) {
var dayOffset = new Date().getDay();
return lynt_get_date(dayOffset + pocet_dni);
}
/**
* Adds specified number of milliseconds to the specified date.
*
* This function correctly transforms between normal time and DST.
*/
function lynt_date_add(date, miliseconds) {
var newTime = date.getTime() + miliseconds;
var newDate = new Date(newTime);
var offsetDiff = newDate.getTimezoneOffset() - date.getTimezoneOffset();
if (offsetDiff != 0) {
newTime += offsetDiff * 60 * 1000;
newDate.setTime(newTime);
}
return newDate;
}
var SortOrder;
(function(SortOrder) {
SortOrder[SortOrder["ASC"] = 0] = "ASC";
SortOrder[SortOrder["DESC"] = 1] = "DESC";
})(SortOrder || (SortOrder = {}));
/**
* Creates or opens specified sheet in spreadsheet.
*/
function lynt_create_or_get_sheet(spreadsheet, name, position) {
var sheet = spreadsheet.getSheetByName(name);
if (sheet == null) {
sheet = spreadsheet.insertSheet(name, position);
}
return sheet;
}
function lynt_remove_empty_sheets(spreadsheet) {
var sheets = spreadsheet.getSheets();
for (var i = 0; i < sheets.length; i++) {
if (sheets[i].getLastRow() == 0) {
spreadsheet.deleteSheet(sheets[i]);
}
}
}
function lynt_highlight_range(range, higlightingRules) {
var rows = range.getNumRows();
var cols = range.getNumColumns();
var values = range.getValues();
var backgrounds = new Array(rows);
var value, color;
for (var r = 0; r < rows; r++) {
backgrounds[r] = new Array(cols);
for (var c = 0; c < cols; c++) {
color = '#FFFFFF';
value = values[r][c];
if (typeof(value) == 'number') {
for (var ri = 1; ri < higlightingRules.length; ri++) {
if (value <= higlightingRules[ri].value) {
color = lynt_color_to_hex(lynt_get_color_from_range(value, higlightingRules[ri - 1], higlightingRules[ri]));
break;
}
}
}
backgrounds[r][c] = color;
}
}
range.setBackgrounds(backgrounds);
}
/**
* Computes color from the given range.
*/
function lynt_get_color_from_range(value, upper, lower) {
var ratio = (value - lower.value) / (upper.value - lower.value);
return {
r: lower.color.r + ratio * (upper.color.r - lower.color.r),
g: lower.color.g + ratio * (upper.color.g - lower.color.g),
b: lower.color.b + ratio * (upper.color.b - lower.color.b),
};
}
function lynt_color_to_hex(color) {
var rHex = Math.round(color.r).toString(16);
var gHex = Math.round(color.g).toString(16);
var bHex = Math.round(color.b).toString(16);
if (rHex.length < 2) {
rHex = '0' + rHex;
}
if (gHex.length < 2) {
gHex = '0' + gHex;
}
if (bHex.length < 2) {
bHex = '0' + bHex;
}
return '#' + rHex + gHex + bHex;
}
function lynt_store_save(dataFolderName, fileName, object) {
var objectJson = JSON.stringify(object);
var dataFolder = lynt_get_data_folder(dataFolderName);
var files = dataFolder.getFilesByName(fileName);
var dataFile;
if (files.hasNext()) {
dataFile = files.next();
dataFile.setContent(objectJson);
}
else {
dataFolder.createFile(fileName, objectJson, "application/json");
}
return true;
}
function lynt_store_load(dataFolderName, fileName) {
var dataFolder = lynt_get_data_folder(dataFolderName);
var files = dataFolder.getFilesByName(fileName);
var dataFile;
if (files.hasNext()) {
dataFile = files.next();
}
if (dataFile) {
var objectJson = dataFile.getBlob().getDataAsString("UTF-8");
return JSON.parse(objectJson);
}
else {
return null;
}
}
function lynt_get_data_folder(dataFolderName) {
var root = DriveApp.getRootFolder();
var folders = root.getFoldersByName(dataFolderName);
var dataFolder;
if (folders.hasNext()) {
dataFolder = folders.next();
}
else {
dataFolder = root.createFolder(dataFolderName);
}
return dataFolder;
}
Komentáře 1
Milan Merglevský
Skvělá práce!