(function () {
var $injector = angular.injector(['ng']);
$injector.invoke(function ($http, $rootScope) {
$rootScope.$apply(function () {
$http.post('/rest/user/login', {
}).then(function successCallback(res) {
angular.module("app").constant("USER", res.data.user);
angular.bootstrap(document, ['app']);
});
});
});
})();
var app = angular.module('app', ['ui.bootstrap', 'amChartsDirective', 'ngRoute', 'pascalprecht.translate']);
app.filter('html', ['$sce', function ($sce) {
return function (text) {
return $sce.trustAsHtml(text);
};
}]);
app.filter('useFilter', function ($filter) {
return function () {
if (typeof arguments[0] == 'undefined') {
return '';
};
var filterName = [].splice.call(arguments, 1, 1)[0];
return $filter(filterName).apply(null, arguments);
};
})
app.filter('useFilter2', function ($filter) {
return function () {
if (typeof arguments[0] == 'undefined') {
return '--';
};
var filterName = [].splice.call(arguments, 1, 1)[0];
return $filter(filterName).apply(null, arguments);
};
})
app.filter('exists', function ($filter) {
return function (v) {
if (v === 0 || v == '0.00') {
return '--';
}
if (!v) {
return '';
};
return v;
};
})
app.filter('secondsToHHmmss', function ($filter) {
return function (seconds) {
if (seconds) {
return $filter('date')(new Date(0, 0, 0).setSeconds(seconds), 'HH:mm:ss');
}
return '00:00:00';
};
})
app.filter('mcdate', function ($filter) {
return function (datum) {
var d = new Date(datum);
return d.getDate() + '/' + (d.getMonth() + 1) + '/' + d.getFullYear() + ' - ' + d.getHours() + ':' + d.getMinutes();
};
})
app.filter('minzero', function ($filter) {
return function (int) {
return (int < 0) ? 0 : int;
};
})
app.filter('path', function () {
return function (url) {
return url = url.substring(url.indexOf("/"));
};
})
app.filter('truefalse', function () {
return function (b) {
return (b) ? 'Yes' : 'No';
};
})
app.filter('string', function () {
return function (b) {
return b;
};
})
app.filter('objToArray', function () {
return function (input, attribute) {
if (!angular.isObject(input)) return input;
var array = [];
for (var objectKey in input) {
array.push(input[objectKey]);
}
return array;
}
})
app.factory('h', function ($rootScope, $http) {
return {
siteId: function () {
if ($rootScope.user.site) {
if ($rootScope.user.site._id) {
return $rootScope.user.site._id.$oid;
}
}
},
login: function (cb) {
$http.post('/rest/user/login', {
}).then(function successCallback(res) {
$rootScope.user = res.data.user;
cb();
});
},
colors: function () {
return angular.extend([], ['#E91E63', '#3F51B5', '#00BCD4', '#8BC34A', '#FF9800', '#795548', '#607D8B', '#00CC00', '#0000CC', '#DDDDDD', '#999999', '#333333', '#990000'], $rootScope.user.reseller.config.chartcolors)
},
metrics: function (key) {
var allMetrics = {
analytics: {
sessions: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
users: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
pageviews: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
pageviewsPerSession: { typefilter: 'number', filtercomparator: 2, 'datemerge': 'avg' },
avgSessionDuration: { typefilter: 'secondsToHHmmss', filtercomparator: 0, 'datemerge': 'avg' },
percentNewSessions: { typefilter: 'number', filtercomparator: 0, 'after': '%', 'datemerge': 'avg' },
bounceRate: { typefilter: 'number', filtercomparator: 0, 'after': '%', 'datemerge': 'avg', 'reversesign': true },
goalCompletionsAll: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
goalValueAll: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
goalConversionRateAll: { typefilter: 'number', filtercomparator: 0, 'after': '%', 'datemerge': 'avg' },
goalAbandonRateAll: { typefilter: 'number', filtercomparator: 0, 'after': '%', 'datemerge': 'avg' },
uniquePageviews: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
avgTimeOnPage: { typefilter: 'secondsToHHmmss', filtercomparator: 0, 'datemerge': 'avg' },
entrances: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
exitRate: { typefilter: 'number', filtercomparator: 0, 'after': '%', 'datemerge': 'avg' },
pageValue: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
exits: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
ROAS: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'avg' },
costPerTransaction: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'avg' },
totalValue: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
adCost: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
CPC: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'avg' },
adClicks: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
transactions: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
uniquePurchases: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
totalValue: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
revenuePerTransaction: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'avg' },
itemQuantity: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
itemsPerPurchase: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'avg' },
},
copernica: {
Placeholder: { name: '', typefilter: 'number', filtercomparator: 0 },
subject: { name: 'Subject', typefilter: 'number', filtercomparator: 0 },
type: { name: 'Type', typefilter: 'string' },
destinations: { name: 'Destinations', typefilter: 'number', filtercomparator: 0 },
abuses: { name: 'Abuses', typefilter: 'number', filtercomparator: 0 },
clicks: { name: 'Clicks', typefilter: 'number', filtercomparator: 0 },
uniqueclicks: { name: 'Unique Clicks', typefilter: 'number', filtercomparator: 0 },
deliveries: { name: 'Deliveries', typefilter: 'number', filtercomparator: 0 },
errors: { name: 'Errors', typefilter: 'number', filtercomparator: 0 },
impressions: { name: 'Impressions', typefilter: 'number', filtercomparator: 0 },
uniqueimpressions: { name: 'Unique Impressions', typefilter: 'number', filtercomparator: 0 },
retries: { name: 'Retries', typefilter: 'number', filtercomparator: 0 },
unsubscribes: { name: 'Unsubscribes', typefilter: 'number', filtercomparator: 0 },
},
facebookads: {
clicks: { name: 'Klikken', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
impressions: { name: 'Impressies', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
cpc: { name: 'cpc', typefilter: 'number', filtercomparator: 2, 'before': '€', 'datemerge': 'avg' },
cpm: { name: 'cpm', typefilter: 'number', filtercomparator: 2, 'before': '€', 'datemerge': 'avg' },
ctr: { name: 'Ctr', typefilter: 'number', filtercomparator: 2, 'after': '%', 'datemerge': 'avg' },
Conversions: { name: 'Conversions', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
Cost: { name: 'Cost', typefilter: 'number', filtercomparator: 0, 'before': '€', 'datemerge': 'sum' },
CostPerConversion: { name: 'spend / conversie', typefilter: 'number', filtercomparator: 0, 'before': '€', 'datemerge': 'avg' },
ConversionRate: { name: 'Conversie ratio', typefilter: 'number', filtercomparator: 0, 'after': '%', 'datemerge': 'avg' },
AveragePosition: { name: 'Gemiddelde positie', typefilter: 'number', filtercomparator: 0, 'datemerge': 'avg' },
ConversionValue: { name: 'Totale conversiewaarde', typefilter: 'number', filtercomparator: 0, 'before': '€', 'datemerge': 'sum' },
relevance_score: { name: 'Relevantie Score', typefilter: 'number', filtercomparator: 0, hide: { 'panel': true }, 'datemerge': 'avg' },
spend: { name: 'Kosten', typefilter: 'number', filtercomparator: 2, 'before': '€', 'datemerge': 'sum' },
},
facebookinsights: {
fans: { name: 'Fans', typefilter: 'number', filtercomparator: 0 },
interacties: { name: 'Interactions', typefilter: 'number', filtercomparator: 0 },
bereik: { name: 'Bereik', typefilter: 'number', filtercomparator: 0 },
},
googlexads: {
Clicks: { name: 'Clicks', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
Impressions: { name: 'Impressions', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
Date: { name: 'Date', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
AverageCpc: { name: 'Avg cost / Click', typefilter: 'number', filtercomparator: 2, 'before': '€', 'datemerge': 'avg' },
Ctr: { name: 'Ctr', typefilter: 'number', filtercomparator: 0, 'after': '%', 'datemerge': 'avg' },
Conversions: { name: 'Conversions', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
Cost: { name: 'Cost', typefilter: 'number', filtercomparator: 2, 'before': '€', 'datemerge': 'sum' },
CostPerConversion: { name: 'Cost / conversion', typefilter: 'number', filtercomparator: 2, 'before': '€', 'datemerge': 'avg' },
ConversionRate: { name: 'Conversion rate', typefilter: 'number', filtercomparator: 0, 'after': '%', 'datemerge': 'avg' },
AbsoluteTopImpressionPercentage: { name: 'AbsoluteTopImpressionPercentage', typefilter: 'number', filtercomparator: 0, 'after': '%', 'datemerge': 'avg' },
ConversionValue: { name: 'Conversion value', typefilter: 'number', filtercomparator: 0, 'before': '€', 'datemerge': 'sum' },
SearchImpressionShare: { name: 'Zoekimpressie aandeel', typefilter: 'number', filtercomparator: 2, 'after': '%', 'datemerge': 'avg' },
SearchAbsoluteTopImpressionShare: { name: 'Top Positie', typefilter: 'number', filtercomparator: 2, 'after': '%', 'datemerge': 'avg' },
roas: { name: 'ROAS', typefilter: 'number', filtercomparator: 2, 'after': '%', 'datemerge': 'avg' },
},
instagram: {
impressions: { name: 'Fans', typefilter: 'number', filtercomparator: 0 },
reach: { name: 'Interactions', typefilter: 'number', filtercomparator: 0 },
follower_count: { name: 'Bereik', typefilter: 'number', filtercomparator: 0 },
profile_views: { name: 'Bereik', typefilter: 'number', filtercomparator: 0 },
website_clicks: { name: 'Bereik', typefilter: 'number', filtercomparator: 0 },
email_contacts: { name: 'Bereik', typefilter: 'number', filtercomparator: 0 },
get_directions_clicks: { name: 'Bereik', typefilter: 'number', filtercomparator: 0 },
phone_call_clicks: { name: 'Bereik', typefilter: 'number', filtercomparator: 0 },
text_message_clicks: { name: 'Bereik', typefilter: 'number', filtercomparator: 0 },
},
linkedin: {
Placeholder: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
shareCount: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
likeCount: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
engagement: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
clickCount: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
impressionCount: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
commentCount: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
networksize: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
networksizeexternal: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
networksizeinternal: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
},
linkedinxads: {
impressions: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
clicks: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
oneClickLeads: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
costInLocalCurrency: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
externalWebsiteConversions: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
costInLocalCurrency: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
networksize: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
networksizeexternal: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
networksizeinternal: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
},
mailchimp: {
Placeholder: { name: '', typefilter: 'number', filtercomparator: 0 },
member_count: { name: 'Aanmeldingen', typefilter: 'number', filtercomparator: 0 },
list_rating: { name: 'Rating', typefilter: 'number', filtercomparator: 0 },
click_rate: { name: 'Klik Ratio', typefilter: 'number', filtercomparator: 0, 'after': '%' },
open_rate: { name: 'Open Ratio', typefilter: 'number', filtercomparator: 0, 'after': '%' },
emails_sent: { name: 'Email verstuurd', typefilter: 'number', filtercomparator: 0 },
send_time: { name: 'Verstuurd op', typefilter: 'mcdate', filtercomparator: 0 },
unsubscribed: { name: 'Afmeldingen', typefilter: 'number', filtercomparator: 0 },
unsubscribe_count: { name: 'Afmeldingen', typefilter: 'number', filtercomparator: 0 },
cleaned_count: { name: 'Verwijderd', typefilter: 'number', filtercomparator: 0 },
bounces: { name: 'Bounce', typefilter: 'number', filtercomparator: 0 },
total_orders: { name: 'Orders', typefilter: 'number', filtercomparator: 0 },
recipient_clicks: { name: 'klikken', typefilter: 'number', filtercomparator: 0 },
total_revenue: { name: 'Totale opbrangest', typefilter: 'number', filtercomparator: 0, 'after': '€' },
},
mybusiness: {
Placeholder: { name: '', typefilter: 'number', filtercomparator: 0 },
QUERIES_DIRECT: { name: 'Directe zoekopdrachten', typefilter: 'number', filtercomparator: 0 },
QUERIES_INDIRECT: { name: 'Indirecte zoekopdrachten', typefilter: 'number', filtercomparator: 0 },
VIEWS_MAPS: { name: 'Google maps', typefilter: 'number', filtercomparator: 0 },
VIEWS_SEARCH: { name: 'Google zoeken', typefilter: 'number', filtercomparator: 0 },
QUERIES_DIRECT: { name: 'Bedrijf zoekopdacht', typefilter: 'number', filtercomparator: 0 },
QUERIES_INDIRECT: { name: 'Categorie zoekopdracht', typefilter: 'number', filtercomparator: 0 },
PHOTOS_VIEWS_MERCHANT: { name: 'Van eigenaars', typefilter: 'number', filtercomparator: 0 },
PHOTOS_VIEWS_CUSTOMERS: { name: 'Van bezoekers', typefilter: 'number', filtercomparator: 0 },
PHOTOS_COUNT_MERCHANT: { name: 'Van eigenaar', typefilter: 'number', filtercomparator: 0 },
PHOTOS_COUNT_CUSTOMERS: { name: 'Van bezoekers', typefilter: 'number', filtercomparator: 0 },
Date: { name: 'Datum', typefilter: 'number', filtercomparator: 0 },
AverageCpc: { name: 'Gemiddelde kosten per klik', typefilter: 'number', filtercomparator: 2, 'before': '€' },
Ctr: { name: 'Ctr', typefilter: 'number', filtercomparator: 0, 'after': '%' },
Conversions: { name: 'Conversions', typefilter: 'number', filtercomparator: 0 },
Cost: { name: 'Cost', typefilter: 'number', filtercomparator: 0, 'before': '€' },
CostPerConversion: { name: 'Kosten per conversie', typefilter: 'number', filtercomparator: 0, 'before': '€' },
ConversionRate: { name: 'Conversie ratio', typefilter: 'number', filtercomparator: 0, 'after': '%' },
AveragePosition: { name: 'Gemiddelde positie', typefilter: 'number', filtercomparator: 1 },
ConversionValue: { name: 'Totale conversiewaarde', typefilter: 'number', filtercomparator: 0, 'before': '€' },
},
searchconsole: {
clicks: { typefilter: 'number', filtercomparator: 0, 'reverse': true, 'datemerge': 'sum' },
ctr: { typefilter: 'number', filtercomparator: 2, 'after': '%', 'reverse': true, 'datemerge': 'avg' },
impressions: { typefilter: 'number', filtercomparator: 0, 'reverse': true, 'datemerge': 'sum' },
Date: { typefilter: 'number', filtercomparator: 0 },
position: { typefilter: 'number', filtercomparator: 1, 'reverse': false, 'reversesign': true, 'datemerge': 'avg' },
},
youtube: {
commentCount: { name: 'Clicks', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
subscriberCount: { name: 'Impressions', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
videoCount: { name: 'Date', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
viewCount: { name: 'Avg cost / Click', typefilter: 'number', filtercomparator: 0, 'datemerge': 'avg' },
views: { name: 'Ctr', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
shares: { name: 'Conversions', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
likes: { name: 'Cost', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
dislikes: { name: 'Cost / conversion', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
comments: { name: 'Conversion rate', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
subscribersGained: { name: 'AbsoluteTopImpressionPercentage', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
subscribersLost: { name: 'Conversion value', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
estimatedMinutesWatched: { name: 'Zoekimpressie aandeel', typefilter: 'number', filtercomparator: 1, 'datemerge': 'sum' },
annotationCloseRate: { name: 'Top Positie', typefilter: 'number', filtercomparator: 2, 'after': '%', 'datemerge': 'avg' },
annotationClickThroughRate: { name: 'ROAS', typefilter: 'number', filtercomparator: 2, 'after': '%', 'datemerge': 'avg' },
averageViewDuration: { name: 'ROAS', typefilter: 'secondsToHHmmss', filtercomparator: 0, 'datemerge': 'avg' },
},
analytics4: {
totalUsers: { name: 'totalUsers', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
screenPageViews: { name: 'screenPageViews', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
screenPageViewsPerSession: { name: 'screenPageViewsPerSession', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
sessions: { name: 'Sessions', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
newUsers: { name: 'Users', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
pageviews: { name: 'Pageviews', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
pageviewsPerSession: { name: 'Pageviews / session', typefilter: 'number', filtercomparator: 2, 'datemerge': 'avg' },
averageSessionDuration: { name: 'Average session duration', typefilter: 'secondsToHHmmss', filtercomparator: 0, 'datemerge': 'avg' },
percentNewSessions: { name: 'Percentrage new sessions', typefilter: 'number', filtercomparator: 0, 'after': '%', 'datemerge': 'avg' },
bounceRate: { name: 'Bounce Ratio', typefilter: 'number', filtercomparator: 0, 'after': '%', 'datemerge': 'avg' },
goalCompletionsAll: { name: 'Goals', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
goalValueAll: { name: 'Goalvalue', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
goalConversionRateAll: { name: 'Goalconversion ratio', typefilter: 'number', filtercomparator: 0, 'after': '%', 'datemerge': 'avg' },
goalAbandonRateAll: { name: 'Doel verlatings ratio', typefilter: 'number', filtercomparator: 0, 'after': '%', 'datemerge': 'avg' },
uniquePageviews: { name: 'Unique Pageviews', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
avgTimeOnPage: { name: 'Avg time on page', typefilter: 'secondsToHHmmss', filtercomparator: 0, 'datemerge': 'avg' },
entrances: { name: 'Entrances', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
exitRate: { name: 'Exit rate', typefilter: 'number', filtercomparator: 0, 'after': '%', 'datemerge': 'avg' },
pageValue: { name: 'Pagevalue', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
exits: { name: 'Exits', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
ROAS: { name: 'ROAS', typefilter: 'number', filtercomparator: 0, 'datemerge': 'avg' },
costPerTransaction: { name: 'Cost per transaction', typefilter: 'number', filtercomparator: 0, 'datemerge': 'avg' },
totalValue: { name: 'Total value', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
adCost: { name: 'Ad Cost', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
CPC: { name: 'CPC', typefilter: 'number', filtercomparator: 0, 'datemerge': 'avg' },
adClicks: { name: 'Ad Clicks', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
transactions: { name: 'Transacions', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
uniquePurchases: { name: 'Unique Purchases', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
totalValue: { name: 'Total Value', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
revenuePerTransaction: { name: 'Revenue per Transaction', typefilter: 'number', filtercomparator: 0, 'datemerge': 'avg' },
itemQuantity: { name: 'Item Quentity', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
itemsPerPurchase: { name: 'items / purchase', typefilter: 'number', filtercomparator: 0, 'datemerge': 'avg' },
eventCount: { name: 'items / purchase', typefilter: 'number', filtercomparator: 0, 'datemerge': 'avg' },
eventCountPerUser: { name: 'items / purchase', typefilter: 'number', filtercomparator: 0, 'datemerge': 'avg' },
mobileDeviceBranding: { name: 'Mobile device model', typefilter: 'text', filtercomparator: 3 },
itemsPurchased: { name: 'Items purchased', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
itemRevenue: { name: 'Item revenue', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
conversions: { name: 'Conversions', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
eventValue: { name: 'Event value', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
sessionConversionRate: { name: 'Session conversion rate', typefilter: 'number', filtercomparator: 0, 'after': '%', 'datemerge': 'avg' },
userEngagementDuration: { name: 'User engagement duration', typefilter: 'secondsToHHmmss', filtercomparator: 0, 'datemerge': 'sum' },
engagementRate: { name: 'User engagement rate', typefilter: 'number', filtercomparator: 2, 'after': '%', 'datemerge': 'avg' },
}
}
return allMetrics[key];
}
}
});
app.directive('bullet', function ($compile) {
return {
restrict: 'E',
scope: {
type: '='
},
template: '',
};
});
app.directive("clientscript", function () {
var updateScripts = function (element) {
return function (script) {
element.empty();
var scriptTag = angular.element(document.createElement("script"));
scriptTag.text(script)
element.append(scriptTag);
};
};
return {
restrict: "EA",
scope: {
scripts: "="
},
link: function (scope, element) {
scope.$watch("scripts", updateScripts(element));
}
};
})
app.filter('maxserp', function () {
return function (text, length, end) {
if (text == 100 || text === undefined) {
return '--';
}
return text;
}
})
app.directive('change', function () {
return {
restrict: 'E',
replace: true,
scope: {
type: '@'
},
template: '{{change}} ',
link: function (scope) {
scope.$watch('type', function () {
if (scope.type == '0') {
scope.change = '--';
} else if (scope.type < 1) {
scope.change = scope.type + ' ';
scope.class = 'fa fa-long-arrow-down text-warning';
} else {
scope.change = scope.type + ' ';
scope.class = 'fa fa-long-arrow-up text-success';
}
});
}
};
});
app.directive('infiniteScroll', [
'$rootScope', '$window', '$timeout',
function ($rootScope, $window, $timeout) {
return {
link: function (scope, elem, attrs) {
var checkWhenEnabled, handler, scrollDistance, scrollEnabled;
$window = angular.element($window);
scrollDistance = 0;
if (attrs.infiniteScrollDistance != null) {
scope.$watch(attrs.infiniteScrollDistance, function (value) {
return scrollDistance = parseInt(value, 10);
});
}
scrollEnabled = true;
checkWhenEnabled = false;
if (attrs.infiniteScrollDisabled != null) {
scope.$watch(attrs.infiniteScrollDisabled, function (value) {
scrollEnabled = !value;
if (scrollEnabled && checkWhenEnabled) {
checkWhenEnabled = false;
return handler();
}
});
}
handler = function () {
var elementBottom, remaining, shouldScroll, windowBottom;
windowBottom = $window.height() + $window.scrollTop();
elementBottom = elem.offset().top + elem.height();
remaining = elementBottom - windowBottom;
shouldScroll = remaining <= $window.height() * scrollDistance;
if (shouldScroll && scrollEnabled) {
if ($rootScope.$$phase) {
return scope.$eval(attrs.infiniteScroll);
} else {
return scope.$apply(attrs.infiniteScroll);
}
} else if (shouldScroll) {
return checkWhenEnabled = true;
}
};
$window.on('scroll', handler);
scope.$on('$destroy', function () {
return $window.off('scroll', handler);
});
return $timeout((function () {
if (attrs.infiniteScrollImmediateCheck) {
if (scope.$eval(attrs.infiniteScrollImmediateCheck)) {
return handler();
}
} else {
return handler();
}
}), 0);
}
};
}
])
app.filter('cleanurl', function () {
return function (url) {
return url.replace(/(^\w+:|^)\/\//, '');
};
});
app.filter('mstotime', function () {
return function (bytes) {
return (bytes > 1000) ? Math.round(bytes / 10) / 100 + 's' : Math.round(bytes) + 'ms';
};
});
app.filter('filename', function () {
return function (url) {
url = url.split(/[?#]/)[0].replace(/\/$/, "");
return url.substring(url.lastIndexOf('/') + 1)
};
});
app.directive('ngGauge', gaugeMeterDirective)
.provider('ngGauge', gaugeMeterProviderFn);
gaugeMeterProviderFn.$inject = [];
function gaugeMeterProviderFn() {
var defaultOptions = {
size: 200,
value: undefined,
min: 0,
max: 100,
cap: 'butt',
thick: 6,
type: 'full',
foregroundColor: 'rgba(0, 150, 136, 1)',
backgroundColor: 'rgba(0, 0, 0, 0.1)',
duration: 1500,
fractionSize: null,
labelOnly: false,
};
this.setOptions = function (customOptions) {
if (!(customOptions && angular.isObject(customOptions)))
throw new Error('Invalid option type specified in the ngGaugeProvider');
defaultOptions = angular.merge(defaultOptions, customOptions);
};
var ngGauge = {
getOptions: function () {
return angular.extend({}, defaultOptions);
}
};
this.$get = function () {
return ngGauge;
};
}
gaugeMeterDirective.$inject = ['ngGauge'];
function gaugeMeterDirective(ngGauge) {
var tpl = '
' +
'{{prepend}}' +
'{{value | number}}' +
'{{value | number: fractionSize}}' +
'{{append}}' +
'{{ label }}' +
'
';
var Gauge = function (element, options) {
this.element = element.find('canvas')[0];
this.text = element.find('span');
this.legend = element.find('b');
this.unit = element.find('u');
this.context = this.element.getContext('2d');
this.options = options;
this.init();
};
Gauge.prototype = {
init: function () {
this.setupStyles();
this.create(null, null);
},
setupStyles: function () {
this.context.canvas.width = this.options.size;
this.context.canvas.height = this.options.size;
this.context.lineCap = this.options.cap;
this.context.lineWidth = this.options.thick;
var lfs = this.options.size * 0.30,
llh = this.options.size;
this.text.css({
display: 'inline-block',
fontWeight: 'normal',
width: '100%',
position: 'absolute',
textAlign: 'center',
overflow: 'hidden',
textOverflow: 'ellipsis',
fontSize: lfs + 'px',
lineHeight: llh + 'px'
});
this.unit.css({
textDecoration: 'none',
fontSize: '0.6em',
fontWeight: 200,
opacity: 0.8
});
var fs = this.options.labelOnly ? lfs * 0.8 : this.options.size / 13;
var lh = this.options.labelOnly ? llh : (5 * fs) + parseInt(this.options.size);
this.legend.css({
display: 'inline-block',
width: '100%',
position: 'absolute',
textAlign: 'center',
overflow: 'hidden',
textOverflow: 'ellipsis',
fontWeight: 'normal',
fontSize: fs + 'px',
lineHeight: lh + 'px'
});
},
create: function (nv, ov) {
var self = this,
type = this.getType(),
bounds = this.getBounds(type),
duration = this.getDuration(),
min = this.getMin(),
max = this.getMax(),
value = this.clamp(this.getValue(), min, max),
start = bounds.head,
unit = (bounds.tail - bounds.head) / (max - min),
displacement = unit * (value - min),
tail = bounds.tail,
color = this.getForegroundColorByRange(value),
requestID,
startTime;
if (nv && ov) {
displacement = unit * nv - unit * ov;
}
function animate(timestamp) {
timestamp = timestamp || new Date().getTime();
var runtime = timestamp - startTime;
var progress = Math.min(runtime / duration, 1); // never exceed 100%
var previousProgress = ov ? (ov * unit) : 0;
var middle = start + previousProgress + displacement * progress;
self.drawShell(start, middle, tail, color);
if (runtime < duration) {
requestID = window.requestAnimationFrame(function (timestamp) {
animate(timestamp);
});
} else {
cancelAnimationFrame(requestID);
}
}
requestAnimationFrame(function (timestamp) {
startTime = timestamp || new Date().getTime();
animate(timestamp);
});
},
getBounds: function (type) {
var head, tail;
if (type == 'semi') {
head = Math.PI;
tail = 2 * Math.PI;
} else if (type == 'full') {
head = 1.5 * Math.PI;
tail = 3.5 * Math.PI;
} else if (type === 'arch') {
head = 0.8 * Math.PI;
tail = 2.2 * Math.PI;
}
return {
head: head,
tail: tail
};
},
drawShell: function (start, middle, tail, color) {
var
context = this.context,
center = this.getCenter(),
radius = this.getRadius(),
foregroundColor = color,
backgroundColor = this.getBackgroundColor();
this.clear();
middle = Math.max(middle, start); // never below 0%
middle = Math.min(middle, tail); // never exceed 100%
context.beginPath();
context.strokeStyle = backgroundColor;
context.arc(center.x, center.y, radius, middle, tail, false);
context.stroke();
context.beginPath();
context.strokeStyle = foregroundColor;
context.arc(center.x, center.y, radius, start, middle, false);
context.stroke();
},
clear: function () {
this.context.clearRect(0, 0, this.getWidth(), this.getHeight());
},
update: function (nv, ov) {
this.create(nv, ov);
},
destroy: function () {
this.clear();
},
getRadius: function () {
var center = this.getCenter();
return center.x - this.getThickness();
},
getCenter: function () {
var x = this.getWidth() / 2,
y = this.getHeight() / 2;
return {
x: x,
y: y
};
},
getValue: function () {
return this.options.value;
},
getMin: function () {
return this.options.min;
},
getMax: function () {
return this.options.max;
},
getWidth: function () {
return this.context.canvas.width;
},
getHeight: function () {
return this.context.canvas.height;
},
getThickness: function () {
return this.options.thick;
},
getBackgroundColor: function () {
return this.options.backgroundColor;
},
getForegroundColor: function () {
return this.options.foregroundColor;
},
getForegroundColorByRange: function (value) {
var isNumber = function (value) {
return value != undefined && !isNaN(parseFloat(value)) && !isNaN(Number(value));
};
var match = Object.keys(this.options.thresholds)
.filter(function (item) { return isNumber(item) && Number(item) <= value; })
.sort(function (a, b) { return Number(a) > Number(b); }).reverse()[0];
return match !== undefined ? this.options.thresholds[match].color || this.getForegroundColor() : this.getForegroundColor();
},
getLineCap: function () {
return this.options.cap;
},
getType: function () {
return this.options.type;
},
getDuration: function () {
return this.options.duration;
},
clamp: function (value, min, max) {
return Math.max(min, Math.min(max, value));
}
};
return {
restrict: 'E',
replace: true,
template: tpl,
scope: {
append: '@?',
backgroundColor: '@?',
cap: '@?',
foregroundColor: '@?',
label: '@?',
labelOnly: '@?',
prepend: '@?',
size: '@?',
thick: '@?',
type: '@?',
duration: '@?',
value: '=?',
min: '=?',
max: '=?',
thresholds: '=?',
fractionSize: '=?'
},
link: function (scope, element) {
var defaults = ngGauge.getOptions(); // fetching default settings from provider
scope.min = angular.isDefined(scope.min) ? scope.min : defaults.min;
scope.max = angular.isDefined(scope.max) ? scope.max : defaults.max;
scope.value = angular.isDefined(scope.value) ? scope.value : defaults.value;
scope.size = angular.isDefined(scope.size) ? scope.size : defaults.size;
scope.cap = angular.isDefined(scope.cap) ? scope.cap : defaults.cap;
scope.thick = angular.isDefined(scope.thick) ? scope.thick : defaults.thick;
scope.type = angular.isDefined(scope.type) ? scope.type : defaults.type;
scope.duration = angular.isDefined(scope.duration) ? scope.duration : defaults.duration;
scope.labelOnly = angular.isDefined(scope.labelOnly) ? scope.labelOnly : defaults.labelOnly;
scope.foregroundColor = angular.isDefined(scope.foregroundColor) ? scope.foregroundColor : defaults.foregroundColor;
scope.backgroundColor = angular.isDefined(scope.backgroundColor) ? scope.backgroundColor : defaults.backgroundColor;
scope.thresholds = angular.isDefined(scope.thresholds) ? scope.thresholds : {};
scope.fractionSize = angular.isDefined(scope.fractionSize) ? scope.fractionSize : defaults.fractionSize;
var gauge = new Gauge(element, scope);
scope.$watch('value', watchData, false);
scope.$watch('min', watchData, false);
scope.$watch('max', watchData, false);
scope.$watch('cap', watchOther, false);
scope.$watch('thick', watchOther, false);
scope.$watch('type', watchOther, false);
scope.$watch('size', watchOther, false);
scope.$watch('duration', watchOther, false);
scope.$watch('foregroundColor', watchOther, false);
scope.$watch('backgroundColor', watchOther, false);
scope.$watch('thresholds', watchOther, false);
scope.$watch('fractionSize', watchData, false);
scope.$on('$destroy', function () { });
scope.$on('$resize', function () { });
function watchData(nv, ov) {
if (!gauge) return;
if (!angular.isDefined(nv) || angular.equals(nv, ov)) return;
gauge.update(nv, ov);
}
function watchOther(nv, ov) {
if (!angular.isDefined(nv) || angular.equals(nv, ov)) return;
gauge.destroy();
gauge.init();
}
}
};
}
app.config(['$routeProvider', '$locationProvider', 'USER', '$sceProvider', '$translateProvider', function AppConfig($routeProvider, $locationProvider, USER, $sceProvider, $translateProvider) {
angular.lowercase = angular.$$lowercase;
$translateProvider.useStaticFilesLoader({
prefix: '/translations/',
suffix: '.json?r=' + Math.random()
});
/* ZET */
$translateProvider.preferredLanguage(USER.site.locale.lang);
$translateProvider.useSanitizeValueStrategy('sce');
$sceProvider.enabled(false);
$routeProvider
.when('/', {
title: 'APP',
templateUrl: viewfolder + '/home/index.html?r=' + r,
nav: 'dashboard'
})
/* help*/
.when('/help', {
title: 'Help',
templateUrl: viewfolder + '/help/index.html?r=' + r,
nav: 'help'
})
/* uptime-monitor */
.when('/uptime', {
title: 'Uptime',
templateUrl: viewfolder + '/uptime/index.html?r=' + r,
nav: 'uptime'
})
/* mcp server */
.when('/mcp', {
title: 'MCP Server',
templateUrl: viewfolder + '/mcp/index.html?r=' + r,
nav: 'mcp'
})
/* onpage seo*/
.when('/onpage-seo', {
title: 'ONPAGE',
templateUrl: viewfolder + '/onpage/index.status.html?r=' + r,
nav: 'onpage'
})
.when('/onpage-seo/scan/:pageid', {
title: 'ONPAGE SCAN',
templateUrl: viewfolder + '/onpage/scan.html?r=' + r,
nav: 'onpage',
controller: function ($scope, $routeParams) {
$scope.pageid = $routeParams.pageid;
},
})
.when('/onpage-seo/:viewrulelabel', {
title: 'ONPAGE',
templateUrl: viewfolder + '/onpage/index.status.html?r=' + r,
nav: 'onpage',
controller: function ($scope, $routeParams) {
$scope.viewrulelabel = $routeParams.viewrulelabel;
},
})
.when('/onpage-seo/:viewrulelabel/:viewrule', {
title: 'ONPAGE',
templateUrl: viewfolder + '/onpage/index.status.html?r=' + r,
nav: 'onpage',
controller: function ($scope, $routeParams) {
$scope.viewrulelabel = $routeParams.viewrulelabel;
$scope.viewrule = $routeParams.viewrule;
},
})
/* linkbuilding*/
.when('/content/', {
title: 'Content Intelligence',
templateUrl: viewfolder + '/content/index.html?r=' + r,
nav: 'content'
})
.when('/content/:modelid', {
title: 'Content Intelligence',
templateUrl: viewfolder + '/content/model.html?r=' + r,
nav: 'content',
controller: function ($scope, $routeParams) {
$scope.modelid = $routeParams.modelid;
},
})
/* linkbuilding*/
.when('/linkbuilding/assistant', {
title: 'Linkbuilding',
templateUrl: viewfolder + '/linkbuilding/assistant.html?r=' + r,
nav: 'linkbuilding'
})
.when('/linkbuilding/template', {
title: 'Linkbuilding',
templateUrl: viewfolder + '/linkbuilding/template.html?r=' + r,
nav: 'linkbuilding'
})
.when('/linkbuilding/wizzard', {
title: 'Linkbuilding',
templateUrl: viewfolder + '/linkbuilding/wizzard.html?r=' + r,
nav: 'linkbuilding'
})
.when('/linkbuilding/manager', {
title: 'Linkbuilding',
templateUrl: viewfolder + '/linkbuilding/manager.html?r=' + r,
nav: 'linkbuilding'
})
.when('/llm-visibility', {
title: 'llmvisibility',
templateUrl: viewfolder + '/llm-visibility/index.html?r=' + r,
nav: 'llmvisibility'
})
/* reports*/
.when('/analytics/:report/:view', {
title: 'SITES',
templateUrl: viewfolder + '/api/analytics.html?r=' + r,
nav: 'analytics',
controller: function ($scope, $routeParams) {
$scope.urlreport = $routeParams.report;
$scope.urlview = $routeParams.view;
},
})
.when('/analytics4/:report/:view', {
title: 'SITES',
templateUrl: viewfolder + '/api/analytics4.html?r=' + r,
nav: 'analytics4',
controller: function ($scope, $routeParams) {
$scope.urlreport = $routeParams.report;
$scope.urlview = $routeParams.view;
},
})
.when('/matomo/:report/:view', {
title: 'SITES',
templateUrl: viewfolder + '/api/matomo.html?r=' + r,
nav: 'matomo',
controller: function ($scope, $routeParams) {
$scope.urlreport = $routeParams.report;
$scope.urlview = $routeParams.view;
},
})
.when('/searchconsole/:report/:view', {
title: 'SEARCHCONSOLE',
templateUrl: viewfolder + '/api/searchconsole.html?r=' + r,
nav: 'tools',
controller: function ($scope, $routeParams) {
$scope.urlreport = $routeParams.report;
$scope.urlview = $routeParams.view;
},
})
.when('/searchconsole/:report/:view/:urlfilter', {
title: 'SEARCHCONSOLE',
templateUrl: viewfolder + '/api/searchconsole.html?r=' + r,
nav: 'tools',
controller: function ($scope, $routeParams) {
$scope.urlreport = $routeParams.report;
$scope.urlview = $routeParams.view;
$scope.urlfilter = $routeParams.urlfilter;
},
})
.when('/youtube/:report/:view', {
title: 'youtube',
templateUrl: viewfolder + '/api/youtube.html?r=' + r,
nav: 'tools',
controller: function ($scope, $routeParams) {
$scope.urlreport = $routeParams.report;
$scope.urlview = $routeParams.view;
},
})
.when('/hub/:report/:view', {
title: 'Performarnce Hub',
templateUrl: viewfolder + '/api/hub.html?r=' + r,
nav: 'hub',
controller: function ($scope, $routeParams) {
$scope.urlreport = $routeParams.report;
$scope.urlview = $routeParams.view;
},
})
.when('/hub/:report/:view/:urlfilter', {
title: 'Performarnce Hub',
templateUrl: viewfolder + '/api/hub.html?r=' + r,
nav: 'hub',
controller: function ($scope, $routeParams) {
$scope.urlreport = $routeParams.report;
$scope.urlview = $routeParams.view;
$scope.urlfilter = $routeParams.urlfilter;
},
})
.when('/mailchimp/:report/:view', {
title: 'mailchimp',
templateUrl: viewfolder + '/api/mailchimp.html?r=' + r,
nav: 'tools',
controller: function ($scope, $routeParams) {
$scope.urlreport = $routeParams.report;
$scope.urlview = $routeParams.view;
},
})
.when('/mailchimp/:report/:view/:filter', {
title: 'mailchimp',
templateUrl: viewfolder + '/api/mailchimp.html?r=' + r,
nav: 'tools',
controller: function ($scope, $routeParams) {
$scope.urlreport = $routeParams.report;
$scope.urlview = $routeParams.view;
$scope.urlfilter = $routeParams.filter;
},
})
.when('/copernica/:report/:view', {
title: 'copernica',
templateUrl: viewfolder + '/api/copernica.html?r=' + r,
nav: 'tools',
controller: function ($scope, $routeParams) {
$scope.urlreport = $routeParams.report;
$scope.urlview = $routeParams.view;
},
})
.when('/copernica/:report/:view/:filter', {
title: 'copernica',
templateUrl: viewfolder + '/api/copernica.html?r=' + r,
nav: 'tools',
controller: function ($scope, $routeParams) {
$scope.urlreport = $routeParams.report;
$scope.urlview = $routeParams.view;
$scope.urlfilter = $routeParams.filter;
},
})
.when('/facebookads/:report/:view', {
title: 'facebookads',
templateUrl: viewfolder + '/api/facebookxads.html?r=' + r,
nav: 'ads',
controller: function ($scope, $routeParams) {
$scope.urlreport = $routeParams.report;
$scope.urlview = $routeParams.view;
},
})
.when('/facebookinsights/:report/:view', {
title: 'facebookinsights',
templateUrl: viewfolder + '/api/facebookinsights.html?r=' + r,
nav: 'tools',
controller: function ($scope, $routeParams) {
$scope.urlreport = $routeParams.report;
$scope.urlview = $routeParams.view;
},
})
.when('/instagram/:report/:view', {
title: 'instagram',
templateUrl: viewfolder + '/api/instagram.html?r=' + r,
nav: 'tools',
controller: function ($scope, $routeParams) {
$scope.urlreport = $routeParams.report;
$scope.urlview = $routeParams.view;
},
})
.when('/linkedin/:report/:view', {
title: 'linkedin',
templateUrl: viewfolder + '/api/linkedin.html?r=' + r,
nav: 'tools',
controller: function ($scope, $routeParams) {
$scope.urlreport = $routeParams.report;
$scope.urlview = $routeParams.view;
},
})
.when('/linkedinxads/:report/:view', {
title: 'linkedinxads',
templateUrl: viewfolder + '/api/linkedinxads.html?r=' + r,
nav: 'tools',
controller: function ($scope, $routeParams) {
$scope.urlreport = $routeParams.report;
$scope.urlview = $routeParams.view;
},
})
.when('/googleads/:report/:view', {
title: 'googleads',
templateUrl: viewfolder + '/api/googlexads.html?r=' + r,
nav: 'ads',
controller: function ($scope, $routeParams) {
$scope.urlreport = $routeParams.report;
$scope.urlview = $routeParams.view;
},
})
.when('/mybusiness/:report/:view', {
title: 'MYBUSINESS',
templateUrl: viewfolder + '/api/mybusiness.html?r=' + r,
nav: 'tools',
controller: function ($scope, $routeParams) {
$scope.urlreport = $routeParams.report;
$scope.urlview = $routeParams.view;
},
})
.when('/planning', {
title: 'planning',
templateUrl: viewfolder + '/planning/index.html?r=' + r,
nav: 'planning',
})
.when('/site', {
title: 'SITES',
templateUrl: viewfolder + '/site/index.html?r=' + r,
nav: 'sites',
})
.when('/site/upgrade/:orderstate', {
title: 'SITES',
templateUrl: viewfolder + '/site/upgrade.html?r=' + r,
nav: 'sites',
controller: function ($scope, $routeParams) {
$scope.orderstate = $routeParams.orderstate;
},
allowguest: true
})
.when('/site/upgrade', {
title: 'SITES',
templateUrl: viewfolder + '/site/upgrade.html?r=' + r,
nav: 'sites',
allowguest: true
})
.when('/sites', {
title: 'SITES',
templateUrl: viewfolder + '/sites/index.html?r=' + r,
nav: 'sites',
})
.when('/sites/new', {
title: 'Add a new site',
templateUrl: viewfolder + '/sites/first.html?r=' + r,
nav: 'sites',
})
.when('/rank-tracker', {
title: 'Rank Tracker',
templateUrl: viewfolder + '/rank-tracker/index.html?r=' + r,
nav: 'ranktracker',
})
.when('/rank-tracker/competitors', {
title: 'Rank Tracker - Competitors',
templateUrl: viewfolder + '/rank-tracker/competitors.html?r=' + r,
nav: 'ranktracker',
})
.when('/rank-tracker/insights', {
title: 'Rank Tracker - Insights',
templateUrl: viewfolder + '/rank-tracker/insights.html?r=' + r,
nav: 'ranktracker',
})
.when('/keywordtool', {
title: 'Rank Tracker',
templateUrl: viewfolder + '/keywordtool/index.html?r=' + r,
nav: 'keywordtool',
})
.when('/insights', {
title: 'Insights',
templateUrl: viewfolder + '/insights/index.html?r=' + r,
nav: 'insights',
})
.when('/insights/:report', {
title: 'Insights',
templateUrl: viewfolder + '/insights/index.html?r=' + r,
nav: 'tools',
controller: function ($scope, $routeParams) {
$scope.urlreport = $routeParams.report;
},
})
/*report*/
.when('/report', {
title: 'Report',
templateUrl: viewfolder + '/report/index.html?r=' + r,
nav: 'report',
})
.when('/report/:report_id', {
title: 'Report',
templateUrl: viewfolder + '/report/report.html?r=' + r,
nav: 'report',
controller: function ($scope, $routeParams) {
$scope.report_id = $routeParams.report_id;
},
})
.when('/reportpage/:key', {
title: 'Report',
templateUrl: viewfolder + '/report/page.html?r=' + r,
nav: 'report',
controller: function ($scope, $routeParams) {
$scope.key = $routeParams.key;
},
})
/* guest*/
.when('/sites/first', {
title: 'Add your first site',
templateUrl: viewfolder + '/sites/first.html?r=' + r,
nav: 'sites',
allowguest: true,
})
.when('/user/login', {
title: 'LOGIN',
allowguest: true,
nav: 'user',
class: 'fullscreen',
templateUrl: viewfolder + '/user/login.html?r=' + r,
})
.when('/user', {
title: 'USER',
nav: 'user',
templateUrl: viewfolder + '/user/index.html?r=' + r,
})
.when('/user/register', {
title: 'LOGIN',
nav: 'user',
class: 'fullscreen',
templateUrl: viewfolder + '/user/register.html?r=' + r,
allowguest: true,
})
.when('/user/recover', {
title: 'RECOVER PASSWORD',
templateUrl: viewfolder + '/user/recover.html?r=' + r,
nav: 'user',
class: 'fullscreen',
allowguest: true,
})
.when('/settings/reseller', {
title: 'RESELLER',
templateUrl: viewfolder + '/reseller/index.html?r=' + r,
nav: 'user',
allowguest: true,
})
.when('/settings/site', {
title: 'RESELLER',
templateUrl: viewfolder + '/site/index.html?r=' + r,
nav: 'user',
allowguest: false,
})
.otherwise({
redirectTo: '/'
});
// enable html5Mode for pushstate ('#'-less URLs)
$locationProvider.html5Mode(true);
}]);
app.run(['$route', '$rootScope', '$location', '$window', 'USER', function ($route, $rootScope, $location, $window, USER) {
$rootScope.r = r;
$rootScope.user = USER;
//set menu state
$rootScope.app = {
body: { class: ['md-open', 'closed'] }
};
$rootScope.$on('$routeChangeSuccess', function (event, current, prev) {
$rootScope.app = {
body: { class: ['md-open', 'closed'] }
};
$rootScope.modal = {};
if (!$rootScope.user._id && !$route.current.allowguest) {
$location.path('/user/login');
} else if (!$rootScope.user.site._id && !$route.current.allowguest) {
$location.path('/sites/first');
} else if (!$route.current.allowguest && $rootScope.user.site._id && current.originalPath != '/site/upgrade' && $rootScope.user.site.status.expire - (Date.now() / 1000) < 0) {
//if ($rootScope.user.reseller.config.own) {
$location.path('/site/upgrade');
//}
}
$rootScope.app.meta = { nav: $route.current.nav, class: $route.current.class, title: USER.reseller.config.name + ' - ' + $route.current.title, description: $route.current.description };
$rootScope.app.sitesdropdown = false;
});
}])
app.controller('core', ['$scope', '$rootScope', '$location', 'h', '$templateRequest', '$window', '$http', '$route', '$translate', '$http',
function($scope, $rootScope, $location, h, $templateRequest, $window, $http, $route, $translate, $http) {
$scope.fnToggleMenu = function() {
$rootScope.app.body.class = ($rootScope.app.body.class[0] == 'md-open') ? ['md-closed', 'open'] : ['md-open', 'closed'];
}
$scope.fnLogout = function() {
$http.post('/rest/user/logout').then(function successCallback(res) {
h.login(function() { $location.path('/user/login'); });
});
}
$scope.fnModal = function(url) {
$rootScope.modal = {};
$rootScope.modal.show = true;
$rootScope.modal.url = viewfolder+'/' + url + '.html';
}
$scope.fnCloseModal = function() {
$rootScope.modal = {};
}
$scope.fnClick = function(path) {
$location.path(path);
}
$scope.fnGoto = function(path) {
//$location.path(path);
$window.location.href = path;
}
$scope.fnSetLang = function(lang) {
$rootScope.user.site.locale.lang = lang;
$translate.use(lang);
$http.get('/rest/global/lang.set', {
params: {
'lang': lang,
}
});
}
$scope.fnChangeSite = function(site) {
$http.post('/rest/sites/change', { websiteid: site._id.$oid }).then(function successCallback(res) {
h.login(function() {
$rootScope._pages = false;
$rootScope._history = false;
if ($location.path() == '/site/upgrade') {
$scope.fnClick('/');
}
$route.reload();
});
});
}
// de lijst pas opbouwen bij de eerste klik; openen en sluiten doet Bootstrap zelf
// (omdraaien liep uit de pas met Bootstrap: na sluiten zonder keuze bleef de lijst leeg)
$scope.fnSitesDropdown = function(site) {
$rootScope.app.sitesdropdown = true;
}
$scope.fnDaysLeft = function() {
return Math.ceil(($rootScope.user.site.status.expire - (Date.now() / 1000)) / (24 * 60 * 60));
}
$scope.isValidSite = function() {
return ($rootScope.user.site.status.expire > (Date.now() / 1000)) ? true : false;
}
$scope.colors = ['#E91E63', '#3F51B5', '#00BCD4', '#8BC34A', '#FF9800', '#795548', '#607D8B', '#00CC00', '#0000CC', '#DDDDDD', '#999999', '#333333', '#990000']
$scope.fnPdf = function() {
console.log('asdasdasd');
var imgs = document.getElementsByTagName('canvas');
for (var i in imgs) {
if (typeof(imgs[i]) == 'object') {
var img = imgs[i].toDataURL("image/png");
var replace = document.createElement("img");
replace.src = img;
imgs[i].parentNode.replaceChild(replace, imgs[i]);
}
}
$http.post("/rest/report/page.download", {
html: angular.element('.reportcontainer').html()
}).then(function successCallback(res) {
$scope.fnGoto('https://robinssl.seoserver.nl/engels/page.php?website_id=' + h.siteId() + '&reseller_id=' + $scope.user.reseller._id.$oid + '&key=' + res.data.k)
/*$http.get('https://robinssl.seoserver.nl/engels/page.php', { 'params': { 'website_id': h.siteId(), 'reseller_id': $scope.user.reseller._id.$oid, 'key': res.data.k } }).then(function successCallback(pdf) {
var newBlob = new Blob([pdf.data], { type: "application/pdf" })
console.log(newBlob);
});*/
});
}
}
]);
app.controller('home', ['$scope', '$timeout', '$http', '$rootScope', '$filter', 'h',
function($scope, $timeout, $http, $rootScope, $filter, h) {
//if ($rootScope.user.firstrun) {
if ($rootScope.user.firstrun) {
$scope.fnModal('home/modal.firstrun');
}
$scope.fnCloseFirstRun = function() {
$http.get('/rest/user/firstrun.set');
$rootScope.user.firstrun = false;
$scope.fnCloseModal();
}
// favoriet: de huidige site in user.sites (favorieten staan bovenaan in de siteswitcher)
var fnCurrentSite = function() {
return ($rootScope.user.sites || []).find(function(site) {
return site._id && site._id.$oid == h.siteId();
});
}
$scope.isFavorite = function() {
var site = fnCurrentSite();
return !!(site && site.favorite);
}
$scope.fnFavorite = function() {
var site = fnCurrentSite();
if (!site) {
return;
}
site.favorite = !site.favorite;
$http.post('/rest/sites/favorite', { websiteid: h.siteId(), favorite: site.favorite }).then(function successCallback(res) {
if (res.data.cb != 'ok') {
site.favorite = !site.favorite;
}
}, function errorCallback() {
site.favorite = !site.favorite;
});
}
$scope.pagecharts = {};
$scope.curtemplate = {};
$scope.colors = h.colors();
$scope.page = {
'widgets': [{
'type': 'analytics_timeline',
'metrics': ['sessions', 'users', 'pageviews', 'pageviewsPerSession', 'avgSessionDuration', 'percentNewSessions', 'bounceRate', 'goalCompletionsAll', 'goalValueAll', 'goalConversionRateAll', 'uniquePageviews', 'avgTimeOnPage', 'entrances', 'exitRate', 'pageValue', 'transactions', 'uniquePurchases', 'totalValue', 'revenuePerTransaction', 'itemQuantity', 'itemsPerPurchase'],
'metric': 'sessions',
'colspan': 6
}, {
'type': 'analytics_chart',
'dimensions': ['channelGrouping', 'keyword', 'source', 'country', 'userAgeBracket', 'userGender', 'socialNetwork', 'deviceCategory', 'adMatchedQuery', 'productName', 'productCategory', 'pagePath', 'landingPagePath', 'exitPagePath'],
'dimension': 'landingPagePath',
'charts': ['pie', 'bar', 'progress'],
'chart': 'bar',
'metrics': ['sessions', 'users', 'pageviews', 'pageviewsPerSession', 'avgSessionDuration', 'percentNewSessions', 'bounceRate', 'goalCompletionsAll', 'goalValueAll', 'goalConversionRateAll', 'uniquePageviews', 'avgTimeOnPage', 'entrances', 'exitRate', 'pageValue', 'transactions', 'uniquePurchases', 'totalValue', 'revenuePerTransaction', 'itemQuantity', 'itemsPerPurchase'],
'metric': 'sessions',
'colspan': 6
},
{
'type': 'analytics4_timeline',
'class': 'mt-4',
'metrics': ['sessions', 'totalUsers', 'screenPageViews', 'screenPageViewsPerSession', 'averageSessionDuration', 'newUsers', 'bounceRate', 'eventCount', 'eventCountPerUser'],
'metric': 'sessions',
'colspan': 6
},
{
'type': 'searchconsole_timeline',
'class': 'mt-4',
'metrics': ['clicks', 'ctr', 'position', 'impressions'],
'metric': 'clicks',
'colspan': 6
}, {
'type': 'searchconsole_chart',
'dimensions': ['query', 'page', 'country', 'device'],
'dimension': 'query',
'charts': ['pie', 'bar', 'progress'],
'chart': 'bar',
'metrics': ['clicks', 'ctr', 'position', 'impressions'],
'metric': 'clicks',
'colspan': 6
}, {
'type': 'searchconsole_keywords',
'colspan': 6
}, {
'type': 'googleads_timeline',
'class': 'mt-4',
'metrics': ['Clicks', 'Impressions', 'Cost', 'AverageCpc', 'Ctr', 'Conversions', 'CostPerConversion', 'ConversionRate', 'ConversionValue', 'SearchImpressionShare', 'SearchAbsoluteTopImpressionShare', 'roas'],
'metric': 'Clicks',
'colspan': 6
}, {
'type': 'googleads_performance_chart',
'dimensions': ['CampaignName', 'AdGroupName', 'Query'],
'dimension': 'CampaignName',
'charts': ['pie', 'bar', 'progress'],
'chart': 'bar',
'metrics': ['Clicks', 'Impressions', 'Cost', 'AverageCpc', 'Ctr', 'Conversions', 'CostPerConversion', 'ConversionRate', 'ConversionValue', 'SearchImpressionShare', 'SearchAbsoluteTopImpressionShare', 'roas'],
'metric': 'Clicks',
'colspan': 6
}, {
'type': 'googleads_conversion_chart',
'dimensions': ['ConversionTypeName'],
'dimension': 'ConversionTypeName',
'charts': ['pie', 'bar', 'progress'],
'chart': 'bar',
'metrics': ['Conversions', 'CostPerConversion', 'ConversionRate', 'ConversionValue'],
'metric': 'Conversions',
'colspan': 6
}, {
'type': 'facebookinsights_timeline',
'class': 'mt-4',
'metrics': ['page_fans_total', 'page_impressions', 'engagements'],
'metric': 'page_fans_total',
'colspan': 6
}, {
'type': 'facebookads_timeline',
'metrics': ['clicks', 'impressions', 'cpc', 'cpm', 'spend', 'ctr'],
'metric': 'clicks',
'colspan': 6
}, {
'type': 'ranktracer',
'class': 'mt-4',
'colspan': 6
}, {
'type': 'onpage_seo',
'colspan': 12
}, {
'type': 'onpage_changes',
'colspan': 6
}, {
'type': 'onpage_changes_donut',
'colspan': 6
}],
'widths': [
{ 'colspan': 3, 'width': '25%' },
{ 'colspan': 4, 'width': '33%' },
{ 'colspan': 6, 'width': '50%' },
{ 'colspan': 8, 'width': '66%' },
{ 'colspan': 9, 'width': '75%' },
{ 'colspan': 12, 'width': '100%' },
]
}
$scope.fnPctChange = function(o, n) {
if (n == 0) {
return '0%';
}
var pct = ((n - o) / o) * 100;
var npct = $filter('number')(pct, 2);
if (pct < 0) {
return '' + npct + '%';
}
return '+ ' + npct + '%';
}
$scope.do = function(obj, method, param) {
if (obj == 'fnWidgets') {
fnWidgets[method](param);
} else if (obj == 'fnTemplates') {
fnTemplates[method](param);
}
}
var fnTemplates = {
list: function() {
$http.get('/rest/home/template.list', {
params: {
websiteid: h.siteId(),
}
}).then(function successCallback(res) {
$scope.templates = res.data;
});
},
use: function(widgets) {
$scope.widgets = widgets;
fnWidgets.save();
$scope.fnCloseModal();
},
create: function() {
var cleanwidgets = angular.copy($scope.widgets).map(function(c) {
delete c.widget;
return c;
})
$scope.curtemplate.widgets = cleanwidgets;
$http.post('/rest/home/template.save', { websiteid: h.siteId(), 'template': $scope.curtemplate }).then(function successCallback(res) {
$scope.fnCloseModal();
});
},
save: function(template) {
var cleanwidgets = angular.copy($scope.widgets).map(function(c) {
delete c.widget;
return c;
})
template.widgets = cleanwidgets;
template.name = ($scope.curtemplate.name) ? $scope.curtemplate.name : template.name;
template.description = ($scope.curtemplate.description) ? $scope.curtemplate.description : template.description;
$http.post('/rest/home/template.save', { websiteid: h.siteId(), 'template': template }).then(function successCallback(res) {
$scope.fnCloseModal();
});
},
delete: function(template) {
$http.post('/rest/home/template.delete', template).then(function successCallback(res) {
$scope.fnCloseModal();
});
},
}
var fnWidgets = {
'add': function(widget) {
$http.post('/rest/home/widget.add', angular.extend({ websiteid: h.siteId() }, widget)).then(function successCallback(res) {
$scope.fnCloseModal();
fnWidgets.list();
$scope.page.edit = false;
});
},
'save': function() {
$http.post('/rest/home/widgets.save', angular.extend({ websiteid: h.siteId() }, { 'widgets': $scope.widgets })).then(function successCallback(res) {
$scope.fnCloseModal();
fnWidgets.list();
$scope.page.edit = false;
});
},
'delete': function(widget) {
angular.forEach($scope.widgets, function(v, k) {
if (v.k == widget.k) {
$scope.widgets.splice(k, 1);
fnWidgets.save();
}
})
},
'reorder': function(o) {
o.widget.order = o.widget.order - 0.1;
angular.forEach($filter('orderBy')($scope.widgets, 'order', false), function(v, k) {
v.order = k + 1;
});
},
'pre_add': function() {
fnTemplates.list();
$scope.fnModal('home/modal.widgets');
},
'pre_edit': function() {
$scope.page.edit = true;
},
'list': function() {
$http.get('/rest/home/widgets.list', {
params: {
websiteid: h.siteId(),
}
}).then(function successCallback(res) {
if (res.data.length == 0) {
res.data = [
{ "type": "onpage_seo", "metric": null, 'colspan': 12, 'order': 1, 'k': 'k1' },
{ "type": "analytics4_timeline", "metric": "sessions", 'colspan': 4, 'order': 2, 'k': 'k2' },
{ "type": "onpage_changes", "metric": null, 'colspan': 4, 'order': 3, 'k': 'k3' },
{ "type": "searchconsole_keywords", "metric": null, 'colspan': 4, 'order': 4, 'k': 'k4' },
{ "type": "searchconsole_timeline", "metric": 'clicks', 'colspan': 6, 'order': 5, 'k': 'k5' },
{ "type": "ranktracer", "metric": null, 'colspan': 6, 'order': 6, 'k': 'k6' },
];
}
$scope.widgets = res.data;
angular.forEach($scope.widgets, function(v, k) {
fnWidgetGenerator[v.type](v);
})
});
}
}
fnWidgets.list();
var _charts = {
init: function(type, dataset, dimension, metric, dateformat) {
return _charts[type](dataset, dimension, metric, dateformat);
},
progress: function(dataset, dimension, metric) {
return $filter('orderBy')(dataset, metric, true).slice(0, 5);
},
bar: function(dataset, dimension, metric) {
dataset = $filter('orderBy')(dataset, metric, true).slice(0, 8);
//inject color attribute with value
if (typeof dataset != undefined) {
for (var i = 0; i < dataset.length; i++) { dataset[i].color = $scope.colors[i]; }
}
return {
data: dataset.slice(0, 6),
type: 'serial',
categoryField: dimension,
rotate: false,
legend: {
enabled: false
},
categoryAxis: {
gridPosition: "start",
parseDates: false,
labelRotation: 0,
gridThickness: 0,
labelFunction: function(value, valueText, axis) { return (value.length > 19) ? value.substr(0, 17) + '...' : value },
},
valueAxes: [{
position: "left",
axisAlpha: 0,
labelsEnabled: true,
dashLength: 4,
color: '#dedede',
}],
graphs: [{
fillColorsField: "color",
balloonText: "[[category]]: [[value]]",
type: "column",
columnWidth: 0.40,
title: "Zoekwoord waarde",
valueField: metric,
fillAlphas: 1,
lineColor: '#009688',
autoColor: true,
}],
}
},
pie: function(dataset, dimension, metric) {
dataset = $filter('orderBy')(dataset, metric, true).slice(0, 12);
return {
data: dataset.slice(0, 9),
type: 'pie',
categoryField: dimension,
"precision": 0,
"decimalSeparator": ",",
"thousandsSeparator": ".",
legend: {
enabled: false,
position: 'right',
autoMargins: false,
marginRight: 100,
},
titleField: dimension,
valueField: metric,
labelRadius: 5,
radius: '42%',
innerRadius: '60%',
labelText: '[[title]]',
}
},
timeline(dataset, dimension, metric, dateformat) {
return {
data: dataset,
type: 'serial',
categoryField: dimension,
rotate: false,
dataDateFormat: dateformat,
categoryAxis: { gridPosition: "start", parseDates: true, gridThickness: 0 },
valueAxes: [{ position: "left", axisAlpha: 0, labelsEnabled: true, dashLength: 4, color: '#dedede', }],
graphs: [{
'balloonText': "Amount
[[value]]",
"bullet": "round",
"bulletBorderAlpha": 1,
"bulletColor": "#FFFFFF",
"useLineColorForBulletBorder": true,
"fillAlphas": 0.3,
"lineThickness": 2,
"lineAlpha": 1,
"bulletSize": 7,
"title": "Expenses",
"valueField": metric,
'lineColor': fnWidgetGenerator.color()
}],
}
}
}
var fnWidgetGenerator = {
i: -1,
'color': function(i) {
var colors = h.colors();
fnWidgetGenerator.i += 1;
return colors[fnWidgetGenerator.i];
},
'analytics_timeline': function(widget) {
$http.get('/rest/analytics/v4', {
params: { 't':'123','dimension': 'date', 'end': 'TODAY', 'metrics': widget.metric, 'start': '30DAYSAGO', websiteid: h.siteId(), }
}).then(function successCallback(res) {
widget.widget = _charts.init('timeline', res.data.data, 'ga:date', widget.metric, 'YYYYMMDD')
widget.widget.action = res.data.action;
});
},
'analytics4_timeline': function(widget) {
$http.get('/rest/analytics4/report', {
params: { 't':'123','dimension': 'date', 'end': 'TODAY', 'metrics': widget.metric, 'start': '30DAYSAGO', websiteid: h.siteId(), }
}).then(function successCallback(res) {
widget.widget = _charts.init('timeline', res.data.data, 'date', widget.metric, 'YYYYMMDD')
widget.widget.action = res.data.action;
});
},
'analytics_chart': function(widget) {
$http.get('/rest/analytics/v4', {
params: { 'dimension': widget.dimension, 'end': 'TODAY', 'metrics': widget.metric, 'start': '30DAYSAGO', websiteid: h.siteId() }
}).then(function successCallback(res) {
widget.widget = _charts.init(widget.chart, res.data.data, 'ga:' + widget.dimension, widget.metric)
widget.widget.action = res.data.action;
});
},
'googleads_timeline': function(widget) {
$http.get('/rest/googlexads/report', {
params: { 'dimension': 'Date', 'end': 'TODAY', 'metrics': widget.metric, 'start': '30DAYSAGO', websiteid: h.siteId(), 'report': 'ACCOUNT_PERFORMANCE_REPORT' }
}).then(function successCallback(res) {
widget.widget = _charts.init('timeline', res.data.data, 'Date', widget.metric, 'YYYY-MM-DD')
widget.widget.action = res.data.action;
});
},
'googleads_performance_chart': function(widget) {
var report = { 'CampaignName': 'CAMPAIGN_PERFORMANCE_REPORT', 'AdGroupName': 'ADGROUP_PERFORMANCE_REPORT', 'Query': 'SEARCH_QUERY_PERFORMANCE_REPORT' };
$http.get('/rest/googlexads/report', {
params: { 'dimension': widget.dimension, 'end': 'TODAY', 'metrics': widget.metric, 'start': '30DAYSAGO', websiteid: h.siteId(), 'report': report[widget.dimension] }
}).then(function successCallback(res) {
widget.widget = _charts.init(widget.chart, res.data.data, widget.dimension, widget.metric)
widget.widget.action = res.data.action;
});
},
'googleads_conversion_chart': function(widget) {
$http.get('/rest/googlexads/report', {
params: { 'dimension': widget.dimension, 'end': 'TODAY', 'metrics': widget.metric, 'start': '30DAYSAGO', websiteid: h.siteId(), 'report': 'ACCOUNT_PERFORMANCE_REPORT' }
}).then(function successCallback(res) {
widget.widget = _charts.init(widget.chart, res.data.data, widget.dimension, widget.metric)
widget.widget.action = res.data.action;
});
},
'facebookinsights_timeline': function(widget) {
$http.get('/rest/facebookinsights/report', {
params: { 'dimension': 'date', 'end': 'TODAY', 'start': '30DAYSAGO', websiteid: h.siteId(), 'endpoint': 'insights', 'report': 'timeline' }
}).then(function successCallback(res) {
if (widget.metric == 'engagements') {
var data = res.data.sets[widget.metric].data.reduce(function(acc, cur) {
if (cur.type == 'engagements') {
acc.push({ 'change': cur.value, 'date': cur.date });
}
return acc;
}, []);
} else {
var data = res.data.sets[widget.metric].data
}
widget.widget = _charts.init('timeline', data, 'date', 'change', 'YYYY-MM-DD')
widget.widget.action = res.data.action;
});
},
'facebookads_timeline': function(widget) {
$http.get('/rest/facebookads/report', {
params: { 'dimension': 'date', 'end': 'TODAY', 'start': '30DAYSAGO', websiteid: h.siteId(), 'level': 'account', 'endpoint': 'insights', 'metrics': widget.metric }
}).then(function successCallback(res) {
widget.widget = _charts.init('timeline', res.data.data, 'date', widget.metric, 'YYYY-MM-DD')
widget.widget.action = res.data.action;
});
},
'onpage_changes': function(widget) {
$http.get('/rest/onpage/rules', { 'params': { 'lang': $scope.user.site.locale.lang } }).then(function successCallback(res) {
$scope.onpage = res.data;
$http.get('/rest/onpage/changes', {
params: {
websiteid: h.siteId(),
}
}).then(function successCallback(res) {
widget.widget = res.data.data;
});
});
},
'onpage_changes_donut': function(widget) {
$http.get('/rest/onpage/changes', { 'params': { websiteid: h.siteId() } }).then(function successCallback(res) {
var stats = [{ 'label': 'change', 'value': 0, 'color': '#808080' }, { 'label': 'done', 'value': 0, 'color': '#4caf50' }, { 'label': 'issue', 'value': 0, 'color': '#ff9800' }];
for (var i in res.data.data) {
if (res.data.data[i].r === true) {
stats[1].value += 1;
} else if (res.data.data[i].r === false) {
stats[2].value += 1;
} else {
stats[0].value += 1;
}
}
widget.widget = { data: stats, type: 'pie', marginLeft: 0, marginRight: 0, marginTop: 10, categoryField: 'label', "precision": 0, "decimalSeparator": ",", "thousandsSeparator": ".", legend: { enabled: false, position: 'right', autoMargins: false, marginRight: 100, }, titleField: 'label', valueField: 'value', "colorField": "color", labelRadius: 5, radius: '42%', innerRadius: '60%', labelText: '[[label]]', }
});
},
'onpage_seo': function(widget) {
$http.get('/rest/onpage/list', {
params: {
websiteid: h.siteId(),
}
}).then(function successCallback(res) {
widget.widget = res.data;
widget.widget.avg = { 'all': 0, 'technical': 0, 'content': 0, 'mobile': 0, 'indexability': 0 }
if(widget.widget.history){
var l = widget.widget.history.length;
angular.forEach(widget.widget.history, function(value, key) {
widget.widget.avg['all'] += value.all / l
widget.widget.avg['technical'] += value.technical / l
widget.widget.avg['content'] += value.content / l
widget.widget.avg['mobile'] += value.mobile / l
widget.widget.avg['indexability'] += value.indexability / l
});
}
});
},
'ranktracer': function(widget) {
$http.get('/rest/hub/report', {
params: {
websiteid: h.siteId(),
context: 'searchconsole',
report: 'gsctracker_list',
dimension: 'query',
}
}).then(function successCallback(res) {
var keywords = (res.data && res.data.data) ? res.data.data : [];
// Sort by clicks descending, take top 8
keywords.sort(function(a, b) {
return (b.metrics_current ? b.metrics_current.clicks : 0) - (a.metrics_current ? a.metrics_current.clicks : 0);
});
widget.widget = keywords.slice(0, 8);
});
},
'searchconsole_keywords': function(widget) {
$http.get('/rest/searchconsole/report', {
params: { 'dimension': 'query', 'end': 'TODAY', 'metrics': 'clicks,ctr,position,impressions', 'start': '30DAYSAGO', 'report': 'searchAnalytics', websiteid: h.siteId(), }
}).then(function successCallback(res) {
widget.widget = res.data;
});
},
'searchconsole_chart': function(widget) {
$http.get('/rest/searchconsole/report', {
params: { 'dimension': widget.dimension, 'end': 'TODAY', 'metrics': widget.metric, 'start': '30DAYSAGO', websiteid: h.siteId(), }
}).then(function successCallback(res) {
widget.widget = _charts.init(widget.chart, res.data.data, widget.dimension, widget.metric);
widget.widget.action = res.data.action;
});
},
'searchconsole_timeline': function(widget) {
$http.get('/rest/searchconsole/report', {
params: { 't':'123','dimension': 'date', 'end': 'TODAY', 'metrics': widget.metric, 'start': '30DAYSAGO', websiteid: h.siteId(), 'report': 'searchAnalytics' }
}).then(function successCallback(res) {
widget.widget = _charts.init('timeline', res.data.data, 'date', widget.metric, 'YYYY-MM-DD')
widget.widget.action = res.data.action;
});
},
};
}
]);
app.controller('help', ['$scope', '$timeout', '$http', '$rootScope', 'h',
function($scope, $timeout, $http, $rootScope, h) {
$http.get('https://www.marketingtracer.com/help/listjson', {
params: {
lang: $rootScope.user.site.locale.lang,
}
}).then(function successCallback(res) {
$scope.topics = res.data;
});
$scope.getText = function(_id, sub_id, subtopic) {
$http.get('https://www.marketingtracer.com/help/subtopicjson', {
params: {
lang: $rootScope.user.site.locale.lang,
_id: _id,
sub_id: sub_id,
}
}).then(function successCallback(res) {
subtopic.html = res.data;
});
}
var users = {
userList: function() {
$http.get('/rest/user/reseller.site.list', {
params: {
websiteid: h.siteId(),
}
}).then(function successCallback(res) {
$scope.users = res.data;
});
},
matchUser: function(UserId) {
for (var i in $scope.users) {
if ($scope.users[i]._id.$oid == UserId) {
return $scope.users[i].name;
}
}
return '';
}
}
}
]);
app.controller("ranktracker", [
"$scope",
"$timeout",
"$http",
"$rootScope",
"$filter",
"h",
function ($scope, $timeout, $http, $rootScope, $filter, h) {
$scope.rankings = [];
$scope.page = {
bFilterStar: false,
label: "",
lgchart: "",
labels: {},
filter: {},
sortType: "q",
collapse: {},
chartlg: false,
locale: $rootScope.user.site.locale,
fnChartLg: function (c) {
this.chartlg = this.chartlg == c ? false : c;
},
sortReverse: false,
fnSort: function (s) {
if ($scope.page.sortType == s) {
$scope.page.sortReverse = !$scope.page.sortReverse;
} else {
$scope.page.sortReverse = false;
}
$scope.page.sortType = s;
},
provider: "google",
};
$scope.fnCustomDate = {
init: function () {
var d = new Date();
d.setMonth(d.getMonth() - 1);
$scope.customdate = { start: d, end: new Date(), show: false };
},
set: function () {
$scope.customdate.show = false;
$rootScope.reportdates.start = Math.round($scope.customdate.start.getTime() / 1000);
$rootScope.reportdates.end = Math.round($scope.customdate.end.getTime() / 1000);
$rootScope.reportdates.description = "Custom";
$scope.getKeywords();
},
};
$scope.dateoptions = [
{ start: false, end: false, description: "All times" },
{ start: "TODAY", end: "TODAY", description: "Today" },
{ start: "YESTYERDAY", end: "YESTYERDAY", description: "Yesterday" },
{ start: "7DAYSAGO", end: "TODAY", description: "Past 7 days" },
{ start: "14DAYSAGO", end: "TODAY", description: "Past 14 days" },
{ start: "30DAYSAGO", end: "TODAY", description: "Past 30 days" },
{ start: "1YEARAGO", end: "TODAY", description: "Past year" },
{ start: "THISMONTH", end: "TODAY", description: "This month" },
{ start: "LASTMONTH", end: "TODAY", description: "Previous month" },
];
if (!$rootScope.reportdates) {
$rootScope.reportdates = { start: false, end: false, description: "All time" };
}
$scope.fnDateSelection = function (reportdates) {
$rootScope.reportdates.start = reportdates.start;
$rootScope.reportdates.end = reportdates.end;
$rootScope.reportdates.description = reportdates.description;
$scope.getKeywords();
$scope.getVisibility();
};
$scope.do = function (obj, method, param) {
if (obj == "fnSingle") {
fnSingle[method](param);
} else {
fnMultiple[method](param);
}
};
var fnSingle = {
getParams: function (ranking) {
return {
websiteid: h.siteId(),
ids: [ranking._id.$oid],
};
},
pre_options: function (ranking) {
ranking.pre_options = !ranking.pre_options;
if (ranking.pre_options) {
$http
.get("/rest/rank-tracker/history", {
params: {
websiteid: h.siteId(),
ranking_id: ranking._id.$oid,
provider: $scope.page.provider,
start: $rootScope.reportdates.start,
end: $rootScope.reportdates.end,
},
})
.then(function successCallback(res) {
var data = res.data;
});
}
},
options: function (ranking) {
$http
.post(
"/rest/rank-tracker/edit",
angular.extend(
{
"summary.google.start": ranking.summary.google.start,
"summary.googlemobile.start": ranking.summary.googlemobile.start,
"meta.label": ranking.meta.label,
url: ranking.url,
fields: ["summary.google.start", "summary.googlemobile.start", "meta.label", "url"],
},
fnSingle.getParams(ranking)
)
)
.then(function successCallback(res) {
$scope.getKeywords();
});
},
sanitize: function (ranking) {
$http.post("/rest/rank-tracker/sanitize", angular.extend({ "q": ranking.q }, fnSingle.getParams(ranking))).then(function successCallback(res) {
$scope.getKeywords();
});
},
delete: function (ranking) {
$http.post("/rest/rank-tracker/delete", fnSingle.getParams(ranking)).then(function successCallback(res) {
$scope.getKeywords();
});
},
star: function (ranking) {
$http.post("/rest/rank-tracker/edit", angular.extend({ "meta.star": !ranking.meta.star, fields: ["meta.star"] }, fnSingle.getParams(ranking))).then(function successCallback(res) {
$scope.getKeywords();
});
},
};
var fnMultiple = {
getParams: function (ranking) {
var ids = [];
for (var i in $scope.rankings) {
if ($scope.rankings[i].checked) {
ids.push($scope.rankings[i]._id.$oid);
}
}
return {
websiteid: h.siteId(),
ids: ids,
};
},
multioptions: function () {
$scope.page.multioptions = false;
for (var i in $scope.rankings) {
if ($scope.rankings[i].checked) {
$scope.page.multioptions = true;
break;
}
}
},
check: function (ranking) {
ranking.checked = !ranking.checked;
fnMultiple.multioptions();
},
checkall: function (label) {
$scope.page.labels[label] = !$scope.page.labels[label];
for (var i in $scope.rankings) {
if ($scope.rankings[i].meta.label == label) {
$scope.rankings[i].checked = $scope.page.labels[label];
}
}
fnMultiple.multioptions();
},
options: function (ranking) {
$http
.post(
"/rest/rank-tracker/edit",
angular.extend(
{
"meta.label": $scope.page.label,
fields: ["meta.label"],
},
fnMultiple.getParams(ranking)
)
)
.then(function successCallback(res) {
$scope.getKeywords();
});
},
delete: function (ranking) {
$http.post("/rest/rank-tracker/delete", fnMultiple.getParams(ranking)).then(function successCallback(res) {
$scope.getKeywords();
});
},
resetvolume: function (ranking) {
$http.post("/rest/rank-tracker/resetvolume", fnMultiple.getParams(ranking)).then(function successCallback(res) {
$scope.getKeywords();
});
},
export: function (ranking) {
$http.post("/rest/rank-tracker/export", fnMultiple.getParams(ranking)).then(function successCallback(res) {
var element = document.createElement("a");
element.setAttribute("href", "data:text/html;," + encodeURIComponent(res.data.csv));
element.setAttribute("download", "export.csv");
element.style.display = "none";
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
});
},
exportxls: function (ranking) {
$http.post("/rest/rank-tracker/exportxls", fnMultiple.getParams(ranking)).then(function successCallback(res) {
var element = document.createElement("a");
element.setAttribute("href", "data:text/html;," + encodeURIComponent(res.data.csv));
element.setAttribute("download", "export.xls");
element.style.display = "none";
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
});
},
};
// ranking grafiek'heschiedenis
$scope.fnRankingHistory = function (ranking, provider) {
if (ranking.active && $scope.page.provider == provider) {
ranking.active = false;
return;
}
$scope.page.provider = provider;
for (var x in $scope.rankings) {
$scope.rankings[x].active = false;
}
ranking.active = true;
$http
.get("/rest/rank-tracker/history", {
params: {
websiteid: h.siteId(),
ranking_id: ranking._id.$oid,
provider: $scope.page.provider,
start: $rootScope.reportdates.start,
end: $rootScope.reportdates.end,
},
})
.then(function successCallback(res) {
var data = res.data;
var graphs = [];
var _d = {};
var d = [];
let urlkeyexists = {};
for (var i in data) {
if (data[i]?.data?.length > 0 || typeof data[i]?.data === "object") {
if (data[i]?.data?.length === 0) {
// stupid array identifies as object!
} else {
console.log(data[i]?.data);
console.log(data[i]?.data?.length);
console.log(typeof data[i]?.data);
for (var y in data[i].data) {
_d[data[i].data[y].date] = _d[data[i].data[y].date] || { date: data[i].data[y].date };
_d[data[i].data[y].date][i] = data[i].data[y].pos;
}
graphs.push({
balloonText: data[i].url + "
position: [[value]]",
title: data[i].url,
bullet: "round",
bulletBorderAlpha: 1,
bulletColor: "#FFFFFF",
useLineColorForBulletBorder: true,
fillAlphas: 0,
lineThickness: 2,
lineAlpha: 1,
bulletSize: 7,
valueField: i,
connect: false,
});
}
}
}
for (var i in _d) {
d.push(_d[i]);
}
ranking.chart = {
data: d,
type: "serial",
theme: "light",
marginLeft: 0,
marginRight: 0,
marginTop: 10,
categoryField: "date",
rotate: false,
pathToImages: "vendor/amchart/images/",
dataDateFormat: "YYYYMMDD",
legend: {
position: "bottom",
enabled: true,
useGraphSettings: true,
},
categoryAxis: {
gridPosition: "start",
parseDates: true,
labelRotation: 45,
},
valueAxes: [
{
reversed: true,
position: "left",
axisAlpha: 0,
labelsEnabled: true,
minimum: 1,
},
],
graphs: graphs,
};
});
};
// haal alle sleutelwoorden op
$scope.getKeywords = function (a) {
// all active plans
$scope.urls = {};
var startDate = $scope.custominit ? $scope.custominit.start : $rootScope.reportdates.start;
$scope.page.bFilterStar = $scope.custominit ? $scope.custominit.bFilterStar : $scope.page.bFilterStar;
if ($scope.page.bFilterStar == true) {
$scope.page.filter.meta = { star: true };
}
$http
.get("/rest/rank-tracker/list", {
params: {
websiteid: h.siteId(),
start: startDate,
end: $rootScope.reportdates.end,
},
})
.then(function successCallback(res) {
$scope.page.rankinglength = res.data.length;
if ($scope.page.rankinglength === 0) {
//$scope.fnModal("rank-tracker/modal.addkeyword");
}
$scope.change = { nverlies: 0, nwinst: 0, nsame: 0, verlies: 0, winst: 0, totaal: 0, n: 0 };
$scope.pie = { top3: 0, top10: 0, top100: 0, overig: 0 };
$scope._rankings = angular.copy(res.data);
//$scope.rankings = res.data;
$scope.sortranking();
});
};
$scope.switchstar = function () {
if ($scope.page.filter.meta) {
delete $scope.page.filter.meta;
} else {
$scope.page.filter.meta = { star: true };
}
$scope.sortranking();
};
$scope.sortranking = function () {
$scope.rankings = $filter("filter")($scope._rankings, $scope.page.filter);
if ($scope.rankings.length > 0) {
for (var i in $scope.rankings) {
if ($scope.rankings[i].active) {
var active = i;
}
}
}
var sorted = {};
// some stats
var totalenhancements = {};
for (var i in $scope.rankings) {
var ranking = $scope.rankings[i];
ranking.meta.label = ranking.meta.label || "--";
if (ranking?.url && ranking.url.indexOf("http") != 0) {
ranking.url = "https://" + ranking.url;
}
var tmpg = ranking?.summary?.google?.pos || 100;
var tmpgm = ranking?.summary?.googlemobile?.pos || 100;
ranking.summary = ranking?.summary || { google: { pos: tmpg }, googlemobile: { pos: tmpgm } };
//ranking.summary.google = ranking?.summary?.google || {pos:100}
//ranking.summary.googlemobile.pos = ranking.summary.googlemobile.pos || 100
if (active && i == active) {
$scope.fnRankingHistory(ranking, $scope.page.provider);
}
// kan weg
if (ranking.summary.google.pos < 4) {
++$scope.pie.top3;
} else if (ranking.summary.google.pos < 11) {
++$scope.pie.top10;
} else if (ranking.summary.google.pos < 100) {
++$scope.pie.top100;
} else {
++$scope.pie.overig;
}
//health
ranking.health = 100 - ((ranking.summary.google.worst - ranking.summary.google.pos + 0.5) / (ranking.summary.google.worst - ranking.summary.google.best)) * 100;
if (ranking.health > 100) {
ranking.health = 100;
}
sorted[ranking.meta.label] = sorted[ranking.meta.label] || [];
sorted[ranking.meta.label].push(ranking);
//change
if (ranking.summary.google.start > ranking.summary.google.pos) {
$scope.change.nwinst += 1;
$scope.change.winst += ranking.summary.google.start - ranking.summary.google.pos;
} else if (ranking.summary.google.start < ranking.summary.google.pos) {
$scope.change.nverlies += 1;
$scope.change.verlies += ranking.summary.google.pos - ranking.summary.google.start;
} else {
$scope.change.nsame += 1;
}
// enhancements
var enhancements = {};
if (typeof ranking.summary.google.enhancements == "object" && ranking.summary.google.enhancements != null) {
if (typeof ranking.summary.google.enhancements.other == "object") {
ranking.summary.google.enhancements.other = ranking.summary.google.enhancements.other || {};
ranking.summary.google.enhancements.other = Object.values(ranking.summary.google.enhancements.other);
}
if (typeof ranking.summary.google.enhancements.my == "object") {
ranking.summary.google.enhancements.my = ranking.summary.google.enhancements.my || {};
ranking.summary.google.enhancements.my = Object.values(ranking.summary.google.enhancements.my);
}
ranking.summary.google.enhancements.other = ranking.summary.google.enhancements.other || [];
ranking.summary.google.enhancements.my = ranking.summary.google.enhancements.my || [];
ranking.summary.google.enhancements.other.forEach(function (v, k) {
totalenhancements[v] = totalenhancements[v] || { v: v, my: 0, other: 0 };
totalenhancements[v].other += 1;
enhancements[v] = enhancements[v] || { v: v, my: 0, other: 0 };
enhancements[v].other += 1;
});
ranking.summary.google.enhancements.my.forEach(function (v, k) {
totalenhancements[v] = totalenhancements[v] || { v: v, my: 0, other: 0 };
totalenhancements[v].my += 1;
enhancements[v] = enhancements[v] || { v: v, my: 0, other: 0 };
enhancements[v].my += 1;
});
}
ranking.enhancements = Object.values(enhancements).sort(function (a, b) {
return a.v < b.v ? -1 : 1;
});
$scope.totalenhancements = Object.values(totalenhancements).sort(function (a, b) {
return a.v < b.v ? -1 : 1;
});
$scope.change.n += 1;
$scope.change.totaal += ranking.summary.google.start - ranking.summary.google.pos;
}
let sortedranking = [];
Object.keys(sorted).forEach(function (k) {
let _sorted = sorted[k];
sortedranking.push({ label: k, rankings: _sorted });
});
$scope.sortedranking = sortedranking;
};
$scope.charts = {};
$scope.changes = {};
var chart = {
small: {
type: "serial",
categoryField: "date",
dataDateFormat: "YYYYMMDD",
autoMargins: false,
categoryAxis: { parseDates: true, gridAlpha: 0, axisAlpha: 0 },
valueAxes: [{ gridAlpha: 0, axisAlpha: 0, color: "#FFFFFF", stackType: "regular" }],
},
large: {
type: "serial",
categoryField: "date",
dataDateFormat: "YYYYMMDD",
autoMargins: true,
categoryAxis: { gridPosition: "start", parseDates: true, gridThickness: 0 },
valueAxes: [{ position: "left", axisAlpha: 0, labelsEnabled: true, dashLength: 4, color: "#dedede", stackType: "regular" }],
},
visibility: function (data, type) {
$scope.charts.visibility = angular.extend({}, chart[type], {
data: data,
graphs: [
{
balloonText: "Visibility: [[value]]%",
fillAlphas: 0,
lineAlpha: 1,
bulletAlpha: 0.7,
valueField: "visibility",
lineColor: h.colors()[1],
},
],
});
$scope.charts.visibilitylg = angular.extend({}, chart["large"], {
data: data,
graphs: [
{
balloonText: "Visibility: [[value]]%",
lineAlpha: 1,
valueField: "visibility",
lineColor: h.colors()[1],
bullet: "round",
bulletBorderAlpha: 1,
bulletColor: "#FFFFFF",
useLineColorForBulletBorder: true,
fillAlphas: 0,
lineThickness: 2,
lineAlpha: 1,
bulletSize: 7,
},
],
});
$scope.changes.visibility = { cur: data[data.length - 1].visibility, start: data[0].visibility, dif: data[data.length - 1].visibility - data[0].visibility };
},
avgpos: function (data, type) {
var _data = data.reduce(function (acc, cur) {
if (cur.avg) {
acc.push(cur);
}
return acc;
}, []);
if (_data.length == 1) {
_data.unshift({ date: "20191031", avg: 0 });
}
$scope.charts.avgpos = angular.extend({}, chart[type], {
data: _data,
graphs: [
{
balloonText: "Avg position: [[value]]",
lineAlpha: 1,
valueField: "avg",
lineColor: h.colors()[3],
},
],
valueAxes: [{ gridAlpha: 0, axisAlpha: 0, color: "#FFFFFF", stackType: "regular", reversed: true }],
});
$scope.charts.avgposlg = angular.extend({}, chart["large"], {
data: _data,
graphs: [
{
balloonText: "Avg position: [[value]]",
valueField: "avg",
lineColor: h.colors()[3],
lineAlpha: 1,
bullet: "round",
bulletBorderAlpha: 1,
bulletColor: "#FFFFFF",
useLineColorForBulletBorder: true,
fillAlphas: 0,
lineThickness: 2,
lineAlpha: 1,
bulletSize: 7,
},
],
valueAxes: [{ gridAlpha: 0, axisAlpha: 0, color: "#FFFFFF", stackType: "regular", reversed: true }],
});
$scope.changes.avgpos = { cur: _data[_data.length - 1].avg, start: _data[0].avg, dif: (_data[_data.length - 1].avg - _data[0].avg) * -1 };
},
enhancement: function (data, type) {
var _data = data.reduce(function (acc, cur) {
if (cur.my_enh || cur.other_enh) {
acc.push(cur);
}
return acc;
}, []);
if (_data.length == 1) {
_data.unshift({ date: "20191031", my_enh: 0, other_enh: 0 });
}
$scope.charts.enhancement = angular.extend({}, chart[type], {
data: _data,
graphs: [
{
balloonText: "My enhancements: [[value]]",
fillAlphas: 0.6,
lineAlpha: 1,
valueField: "my_enh",
lineColor: h.colors()[4],
},
{
balloonText: "Total enhancements: [[value]]",
fillAlphas: 0.6,
lineAlpha: 1,
valueField: "other_enh",
lineColor: "#DDDDDD",
},
],
});
$scope.charts.enhancementlg = angular.extend({}, chart["large"], {
data: _data,
graphs: [
{
balloonText: "My enhancements: [[value]]",
bullet: "round",
lineAlpha: 1,
bullet: "round",
bulletBorderAlpha: 1,
bulletColor: "#FFFFFF",
useLineColorForBulletBorder: true,
fillAlphas: 0.6,
lineThickness: 2,
lineAlpha: 1,
bulletSize: 7,
valueField: "my_enh",
lineColor: h.colors()[4],
},
{
balloonText: "Total enhancements: [[value]]",
bullet: "round",
lineAlpha: 1,
bullet: "round",
bulletBorderAlpha: 1,
bulletColor: "#FFFFFF",
useLineColorForBulletBorder: true,
fillAlphas: 0.6,
lineThickness: 2,
lineAlpha: 1,
bulletSize: 7,
valueField: "other_enh",
lineColor: "#DDDDDD",
},
],
});
$scope.changes.enhancement = { cur: _data[_data.length - 1]?.my_enh, start: _data[0]?.my_enh, dif: _data[_data.length - 1]?.my_enh - _data[0]?.my_enh };
},
top: function (data, type) {
var _data = data.reduce(function (acc, cur) {
if (cur.top3 || cur.top10 || cur.top50 || cur.top100 || cur.norank) {
acc.push(cur);
}
return acc;
}, []);
$scope.charts.top = angular.extend({}, chart[type], {
data: _data,
graphs: [
{ balloonText: "Not ranked: [[value]]", fillAlphas: 0.6, lineAlpha: 0.6, valueField: "norank", lineColor: "#DDDDDD" },
{ balloonText: "Top100: [[value]]", fillAlphas: 0.25, lineAlpha: 0.25, valueField: "top100", lineColor: h.colors()[2] },
{ balloonText: "Top50: [[value]]", fillAlphas: 0.45, lineAlpha: 0.45, valueField: "top50", lineColor: h.colors()[2] },
{ balloonText: "Top10: [[value]]", fillAlphas: 0.65, lineAlpha: 0.65, valueField: "top10", lineColor: h.colors()[2] },
{ balloonText: "Top3: [[value]]", fillAlphas: 0.85, lineAlpha: 0.85, valueField: "top3", lineColor: h.colors()[2] },
{ balloonText: "Top1: [[value]]", fillAlphas: 1, lineAlpha: 1, valueField: "top1", lineColor: h.colors()[2] },
],
});
$scope.changes.top = {
cur: _data[_data.length - 1],
start: _data[0],
dif: {
top1: _data[_data.length - 1].top1 - _data[0].top1 || 0,
top3: _data[_data.length - 1].top3 - _data[0].top3 || 0,
top10: _data[_data.length - 1].top10 - _data[0].top10 || 0,
top50: _data[_data.length - 1].top50 - _data[0].top50 || 0,
top100: _data[_data.length - 1].top100 - _data[0].top100 || 0,
norank: _data[_data.length - 1].norank - _data[0].norank || 0,
},
};
},
};
// haal alle sleutelwoorden op
$scope.getVisibility = function (a) {
$http
.get("/rest/rank-tracker/visibility", {
params: {
websiteid: h.siteId(),
start: $rootScope.reportdates.start,
end: $rootScope.reportdates.end,
},
})
.then(function successCallback(res) {
chart.avgpos(res.data, "small");
chart.visibility(res.data, "small");
chart.enhancement(res.data, "small");
chart.top(res.data, "small");
});
};
$scope.fnAddKeywords = function () {
$http
.post("/rest/rank-tracker/add", {
keywords: $scope.page.addkeywords,
locale: $scope.page.locale,
websiteid: h.siteId(),
})
.then(function successCallback(res) {
$scope.getKeywords();
$scope.fnCloseModal();
$scope.page.addkeywords = "";
});
};
$scope.getKeywords();
$scope.getVisibility();
$http.get("/rest/locale/language.list").then(function successCallback(res) {
$scope.languages = res.data;
});
$http.get("/rest/locale/country.list").then(function successCallback(res) {
$scope.countries = res.data;
});
$scope.fnSearchCity = function (q) {
$http.get("/rest/locale/city.list", { params: { q: q } }).then(function successCallback(res) {
$scope.cities = res.data;
});
};
$scope.setCity = function (city) {
$scope.page.locale.city = city.city;
$scope.page.locale.uule = city.uule;
$scope.cities = [];
};
},
]);
app.controller('ranktrackerinsights', ['$scope', '$timeout', '$http', '$rootScope', '$filter', 'h',
function($scope, $timeout, $http, $rootScope, $filter, h) {
$scope.rankings = [];
if($scope.custominit){
$scope.page = {'timeframe': '31d'};
$scope.mds = [
{ 'title': '31-day mean - position improvement', 'timeframe':'31d', 'start': 'r31d', 'end': 'r62d', 'dif': 's31dc', 'vol': 's31dvol', 'order': '-median.s31dc', 'lengthindex': '31dl', 'comp': 'gt' },
{ 'title': '31-day mean - position loss', 'timeframe':'31d', 'start': 'r31d', 'end': 'r62d', 'dif': 's31dc', 'vol': 's31dvol', 'order': 'median.s31dc', 'lengthindex': '31dw', 'comp': 'lt' },
];
} else {
$scope.page = {'timeframe': '3d'};
$scope.mds = [
{ 'title': '3-day mean - position improvement', 'timeframe':'3d', 'start': 'r3d', 'end': 'r6d', 'dif': 's3dc', 'vol': 's3dvol', 'order': '-median.s3dc', 'lengthindex': '3dw', 'comp': 'gt' },
{ 'title': '3-day mean - traffic improvement', 'timeframe':'3d', 'start': 'r3d', 'end': 'r6d', 'dif': 's3dc', 'vol': 's3dvol', 'order': '-median.s3dvol', 'lengthindex': '3dw', 'comp': 'gt' },
{ 'title': '3-day mean - position loss', 'timeframe':'3d', 'start': 'r3d', 'end': 'r6d', 'dif': 's3dc', 'vol': 's3dvol', 'order': 'median.s3dc', 'lengthindex': '3dl', 'comp': 'lt' },
{ 'title': '3-day mean - traffic loss', 'timeframe':'3d', 'start': 'r3d', 'end': 'r6d', 'dif': 's3dc', 'vol': 's3dvol', 'order': 'median.s3dvol', 'lengthindex': '3dl', 'comp': 'lt' },
{ 'title': '7-day mean - position improvement', 'timeframe':'7d', 'start': 'r7d', 'end': 'r14d', 'dif': 's7dc', 'vol': 's7dvol', 'order': '-median.s7dc', 'lengthindex': '7dw', 'comp': 'gt' },
{ 'title': '7-day mean - traffic improvement', 'timeframe':'7d', 'start': 'r7d', 'end': 'r14d', 'dif': 's7dc', 'vol': 's7dvol', 'order': '-median.s7dvol', 'lengthindex': '7dw', 'comp': 'gt' },
{ 'title': '7-day mean - position loss', 'timeframe':'7d', 'start': 'r7d', 'end': 'r14d', 'dif': 's7dc', 'vol': 's7dvol', 'order': 'median.s7dc', 'lengthindex': '7dl', 'comp': 'lt' },
{ 'title': '7-day mean - traffic loss', 'timeframe':'7d', 'start': 'r7d', 'end': 'r14d', 'dif': 's7dc', 'vol': 's7dvol', 'order': 'median.s7dvol', 'lengthindex': '7dl', 'comp': 'lt' },
{ 'title': '31-day mean - position improvement', 'timeframe':'31d', 'start': 'r31d', 'end': 'r62d', 'dif': 's31dc', 'vol': 's31dvol', 'order': '-median.s31dc', 'lengthindex': '31dl', 'comp': 'gt' },
{ 'title': '31-day mean - traffic improvement', 'timeframe':'31d', 'start': 'r31d', 'end': 'r62d', 'dif': 's31dc', 'vol': 's31dvol', 'order': '-median.s31dvol', 'lengthindex': '31dl', 'comp': 'gt' },
{ 'title': '31-day mean - position loss', 'timeframe':'31d', 'start': 'r31d', 'end': 'r62d', 'dif': 's31dc', 'vol': 's31dvol', 'order': 'median.s31dc', 'lengthindex': '31dw', 'comp': 'lt' },
{ 'title': '31-day mean - traffic loss', 'timeframe':'31d', 'start': 'r31d', 'end': 'r62d', 'dif': 's31dc', 'vol': 's31dvol', 'order': 'median.s31dvol', 'lengthindex': '31dw', 'comp': 'lt' },
];
}
$scope.exists = function(prop, comp) {
return function(item) {
return item.median.hasOwnProperty(prop) && (comp == 'lt') ? item.median[prop] < 0 : item.median[prop] > 0
}
}
var competitors = {
listinsights: function() {
$http.get("/rest/rank-tracker/median", {
params: {
websiteid: h.siteId(),
}
}).then(function successCallback(res) {
$scope.insights = res.data;
$scope.lengths = $scope.insights.reduce(function(acc, cur) {
if (cur.median.s3dc > 0) {
acc['3dw'] += 1;
} else if (cur.median.s3dc < 0) {
acc['3dl'] += 1;
}
if (cur.median.s7dc > 0) {
acc['7dw'] += 1;
} else if (cur.median.s7dc < 0) {
acc['7dl'] += 1;
}
if (cur.median.s7dc > 0) {
acc['31dw'] += 1;
} else if (cur.median.s31dc < 0) {
acc['31dl'] += 1;
}
return acc;
}, { '3dl': 0, '3dw': 0, '7dl': 0, '7dw': 0, '31dl': 0, '31dw': 0 })
});
},
}
competitors.listinsights();
// ranking grafiek'heschiedenis
$scope.fnRankingHistory = function(ranking, provider) {
if (ranking.active && $scope.page.provider == provider) {
ranking.active = false;
return;
}
$scope.page.provider = provider;
for (var x in $scope.rankings) {
$scope.rankings[x].active = false;
}
ranking.active = true;
$http.get("/rest/rank-tracker/history", {
params: {
websiteid: h.siteId(),
ranking_id: ranking._id.$oid,
provider: 'google',
}
}).then(function successCallback(res) {
var data = res.data;
var graphs = [];
var _d = {};
var d = [];
for (var i in data) {
for (var y in data[i].data) {
_d[data[i].data[y].date] = _d[data[i].data[y].date] || { 'date': data[i].data[y].date };
_d[data[i].data[y].date][i] = data[i].data[y].pos;
}
graphs.push({
balloonText: data[i].url + "
position: [[value]]",
"title": data[i].url,
"bullet": "round",
"bulletBorderAlpha": 1,
"bulletColor": "#FFFFFF",
"useLineColorForBulletBorder": true,
"fillAlphas": 0,
"lineThickness": 2,
"lineAlpha": 1,
"bulletSize": 7,
"valueField": i,
"connect": false,
})
}
for (var i in _d) {
d.push(_d[i]);
}
ranking.chart = {
data: d,
type: 'serial',
theme: 'light',
marginLeft: 0,
marginRight: 0,
marginTop: 10,
categoryField: 'date',
rotate: false,
pathToImages: 'vendor/amchart/images/',
dataDateFormat: 'YYYYMMDD',
legend: {
position: "bottom",
enabled: true,
"useGraphSettings": true,
},
categoryAxis: {
gridPosition: "start",
parseDates: true,
labelRotation: 45
},
valueAxes: [{
reversed: true,
position: "left",
axisAlpha: 0,
labelsEnabled: true,
minimum: 1,
}],
graphs: graphs,
}
});
}
// set competitors
$scope.fnAddCompetitors = function() {
$http.post('/rest/rank-tracker/competitors.edit', {
competitors: $scope.competitors,
websiteid: h.siteId()
}).then(function successCallback(res) {
competitors.listinsights();
competitors.listkeywords();
$scope.fnCloseModal();
});
}
}
]);
app.controller('ranktrackercompetitors', ['$scope', '$timeout', '$http', '$rootScope', '$filter', 'h',
function($scope, $timeout, $http, $rootScope, $filter, h) {
$scope.rankings = [];
$scope.blazak = {};
$scope.page = {
label: '',
labels: {},
table: {
'curpage': 0,
'itemsperpage': 14,
'offset': 0,
'filter': {},
'order': { 'k': 'url', 'r': false },
'fnOrder': function(k, b) {
this.order.r = (k == this.order.k) ? !this.order.r : b;
this.order.k = k;
},
fnSetOffset: function() {
this.offset = this.itemsperpage * (this.curpage - 1);
},
fnFilterProperty: function() {
var pages = angular.copy($rootScope._pages);
$scope.pages = $filter('filter')(pages, $scope.page.table.filter);
}
},
date: 'alltime',
dates: ['alltime', '4weeks', '4months', 'thismonth'],
tab: 'my',
provider: 'google'
}
$scope.do = function(obj, method, param) {
if (obj == 'fnSingle') {
fnSingle[method](param);
} else {
fnMultiple[method](param);
}
}
$scope.export = function () {
$http.post("/rest/rank-tracker/exportcompetitors", {
websiteid: h.siteId(),
}).then(function successCallback(res) {
var element = document.createElement("a");
element.setAttribute("href", "data:text/html;," + encodeURIComponent(res.data.csv));
element.setAttribute("download", "export.csv");
element.style.display = "none";
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
});
};
var competitors = {
listcompetitors: function() {
$http.get("/rest/rank-tracker/competitors.list", {
params: {
websiteid: h.siteId(),
}
}).then(function successCallback(res) {
$scope.competitors = res.data;
if ($scope.competitors.length === 0 && $rootScope.user.site.role < 6) {
$scope.fnModal('rank-tracker/modal.editcompetitors');
}
});
},
listkeywords: function() {
if ($scope.rankings.length > 0) {
for (var i in $scope.rankings) {
if ($scope.rankings[i].active) {
var active = i;
}
}
}
var sorted = {};
// all active plans
$scope.urls = {}
$http.get('/rest/rank-tracker/competitors.listkeywords', {
params: {
websiteid: h.siteId(),
}
}).then(function successCallback(res) {
$scope.page.rankinglength = res.data.length;
$scope.rankings = res.data;
// some stats
for (var i in $scope.rankings) {
var ranking = $scope.rankings[i];
ranking.summary.google.pos = ranking.summary.google.pos || 100
ranking.summary.googlemobile.pos = ranking.summary.googlemobile.pos || 100
sorted[ranking.meta.label] = sorted[ranking.meta.label] || [];
sorted[ranking.meta.label].push(ranking);
}
$scope.sortedranking = sorted;
});
}
}
competitors.listcompetitors();
competitors.listkeywords();
// ranking grafiek'heschiedenis
$scope.fnRankingHistory = function(ranking, provider) {
provider = 'google';
console.log(ranking);
if (ranking.active && $scope.page.provider == provider) {
ranking.active = false;
return;
}
$scope.page.provider = provider;
for (var x in $scope.rankings) {
$scope.rankings[x].active = false;
}
ranking.active = true;
$http.get("/rest/rank-tracker/competitors.history", {
params: {
websiteid: h.siteId(),
ranking_id: ranking._id.$oid,
provider: $scope.page.provider,
}
}).then(function successCallback(res) {
var data = res.data;
var graphs = [];
var _d = {};
var d = [];
for (var i in data) {
console.log(data[i]);
for (var y in data[i].data) {
_d[data[i].data[y].date] = _d[data[i].data[y].date] || { 'date': data[i].data[y].date };
_d[data[i].data[y].date][i] = data[i].data[y].pos;
}
graphs.push({
balloonText: data[i].url + "
position: [[value]]",
"title": data[i].url,
"bullet": "round",
"bulletBorderAlpha": 1,
"bulletColor": "#FFFFFF",
"useLineColorForBulletBorder": true,
"fillAlphas": 0,
"lineThickness": 2,
"lineAlpha": 1,
"bulletSize": 7,
"valueField": i,
"connect": false,
})
}
for (var i in _d) {
d.push(_d[i]);
}
ranking.chart = {
data: d,
type: 'serial',
theme: 'light',
marginLeft: 0,
marginRight: 0,
marginTop: 10,
fontFamily: 'Roboto',
categoryField: 'date',
rotate: false,
pathToImages: 'vendor/amchart/images/',
dataDateFormat: 'YYYYMMDD',
legend: {
position: "bottom",
enabled: true,
"useGraphSettings": true,
},
categoryAxis: {
gridPosition: "start",
parseDates: true,
labelRotation: 45
},
valueAxes: [{
reversed: true,
position: "left",
axisAlpha: 0,
labelsEnabled: true,
minimum: 1,
}],
graphs: graphs,
}
});
}
// set competitors
$scope.fnAddCompetitors = function() {
$http.post('/rest/rank-tracker/competitors.edit', {
competitors: $scope.competitors,
websiteid: h.siteId()
}).then(function successCallback(res) {
competitors.listcompetitors();
competitors.listkeywords();
$scope.fnCloseModal();
});
}
}
]);
app.controller('linkbuildingassistant', ['$scope', '$http', '$rootScope', '$window', '$filter', 'h',
function($scope, $http, $rootScope, $window, $filter, h) {
$scope.page = {
'quickselect': {},
'importtab': false,
'categories': [],
'tags': {},
'dates': [],
'kdates': {},
'newlink': { 'moz': { 'targetscope': 'page', 'n': '50' } },
'multiplelinks': {},
'filter': {},
table: {
'curpage': 0,
'itemsperpage': 100,
'offset': 0,
fnSetOffset: function() {
this.offset = this.itemsperpage * (this.curpage - 1);
},
}
}
/* months*/
var thismonth = new Date().getMonth() + 3;
for (var i = 0; i < 15; i++) {
var date = new Date(new Date().getFullYear(), thismonth, 0);
$scope.page.dates.push({ 'date': date });
$scope.page.kdates[date.toString()] = date;
thismonth = thismonth - 1;
}
// do
$scope.do = function(obj, method, param) {
if (obj == 'fnSingle') {
return fnSingle[method](param);
} else if (obj == 'fnWizzard') {
return fnWizzard[method](param);
} else if (obj == 'fnTemplates') {
return fnTemplates[method](param);
} else {
return fnMultiple[method](param);
}
}
// link
var fnLinks = {
list: function() {
$http.get("/rest/linkbuilding/link.list", {
params: {
websiteid: h.siteId(),
}
}).then(function successCallback(res) {
$scope.page.multioptions = false;
$scope.page.checkall = false;
for (var i in res.data) {
var date = new Date(res.data[i].date * 1);
res.data[i].monthstring = date.toLocaleString('default', { year: 'numeric', 'month': 'long' })
res.data[i].d = res.data[i].date*1;
res.data[i].date = $scope.page.kdates[date.toString()];
if (res.data[i].my.tag) {
$scope.page.tags[res.data[i].my.tag] = res.data[i].my.tag;
}
}
res.data = $filter('orderBy')(res.data, 'd', true)
$scope.links = res.data;
});
},
}
// categories
var fnCategories = {
list: function() {
if ($scope.categories) {
return;
}
$http.get('/rest/linkbuilding/manager.categories.list').then(function successCallback(res) {
$scope.categories = res.data;
});
}
}
// categories
var fnTemplates = {
list: function() {
$http.get('/rest/linkbuilding/template.list', {
params: {
'websiteid': h.siteId(),
}
}).then(function successCallback(res) {
$scope.mailTemplates = res.data;
});
},
'get_mails_by_key': function(key) {
for (var i in $scope.mailTemplates) {
if ($scope.mailTemplates[i].key == key) {
return $scope.mailTemplates[i].mails;
}
}
}
}
//wizzard
var fnWizzard = {
'moz-domain': function() {
$http.post("/rest/linkbuilding/wizzard.moz.domain",
angular.extend({ 'domain': $scope.page.mozdomain }, { websiteid: h.siteId() })
).then(function successCallback(res) {
fnLinks.list();
});
},
'add_url': function() {
$http.post("/rest/linkbuilding/link.add.url",
angular.extend($scope.page.newlink, { websiteid: h.siteId(), 'date': new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0) })
).then(function successCallback(res) {
$scope.page.newlink = { 'moz': { 'targetscope': 'page', 'n': '50' } };
fnLinks.list();
});
},
'add_bulk': function() {
$http.post("/rest/linkbuilding/link.add.bulk",
angular.extend({ 'my': $scope.page.newlink.my, 'bulk': $scope.page.newlink.bulk, 'allowduplicates': $scope.page.newlink.allowduplicates }, { websiteid: h.siteId(), 'date': new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0) })
).then(function successCallback(res) {
$scope.page.newlink = { 'moz': { 'targetscope': 'page', 'n': '50' } };
fnLinks.list();
});
},
'add_search': function() {
$http.post("/rest/linkbuilding/link.add.search",
angular.extend($scope.page.newlink, { websiteid: h.siteId(), 'date': new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0) })
).then(function successCallback(res) {
$scope.page.newlink = { 'moz': { 'targetscope': 'page', 'n': '50' } };
fnLinks.list();
});
},
'add_moz': function() {
$http.post("/rest/linkbuilding/link.add.moz",
angular.extend($scope.page.newlink, { websiteid: h.siteId(), 'date': new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0) })
).then(function successCallback(res) {
$scope.page.newlink = { 'moz': { 'targetscope': 'page', 'n': '50' } };
fnLinks.list();
});
},
'list_google_alerts': function() {
$http.get("/rest/linkbuilding/rss.list.google.alerts", {
params: {
websiteid: h.siteId(),
}
}).then(function successCallback(res) {
$scope.page.google_alerts = res.data;
});
},
'add_google_alerts': function() {
$http.post("/rest/linkbuilding/rss.add.google_alerts",
angular.extend({}, { 'alert': $scope.page.newlink.google_alerts }, { websiteid: h.siteId() })
).then(function successCallback(res) {
$scope.page.newlink = { 'moz': { 'targetscope': 'page', 'n': '50' } };
$scope.page.importtab = false;
fnLinks.list();
});
},
'remove_google_alert': function(alert) {
$http.post("/rest/linkbuilding/rss.remove.google.alerts",
angular.extend({}, { 'alert': alert }, { websiteid: h.siteId() })
).then(function successCallback(res) {
fnWizzard.list_google_alerts();
$scope.page.newlink = { 'moz': { 'targetscope': 'page', 'n': '50' } };
});
},
}
$scope.fnCopyText = function(text) {
var body = angular.element($window.document.body);
var textarea = angular.element('');
textarea.css({
position: 'fixed',
opacity: '0'
});
textarea.val(text);
body.append(textarea);
textarea[0].select();
try {
var successful = document.execCommand('copy');
if (!successful) throw successful;
} catch (err) {
}
textarea.remove();
}
// single link actions
var fnSingle = {
'getParams': function(link) {
return {
'websiteid': h.siteId(),
'ids': [link._id.$oid],
}
},
'pre_edit': function(link) {
fnCategories.list();
$scope.page.curlink = link;
$scope.fnModal('linkbuilding/modal/single-edit')
},
'edit': function(link) {
var fields = ['partner.url', 'my.status', 'my.url', 'my.anchor', 'date', 'text', 'trade.anchor', 'trade.url', 'partner.type', 'partner.domain.contactform', 'partner.domain.email', 'partner.domain.manager.email', 'partner.domain.manager.name', 'partner.domain.manager.CatId'];
$http.post("/rest/linkbuilding/link.edit",
angular.extend(link, fnSingle.getParams(link), { 'fields': fields })
).then(function successCallback(res) { fnLinks.list(); })
},
'pre_mail': function(link) {
if (!link.mail && $scope.mailTemplates[0] !== undefined) {
link.mail = { key: $scope.mailTemplates[0].key }
}
$scope.page.curlink = link;
$scope.fnModal('linkbuilding/modal/mail')
},
'mail': function(link) {
$http.post("/rest/linkbuilding/link.mail",
angular.extend(link, fnSingle.getParams(link))
).then(function successCallback(res) {
fnLinks.list();
})
},
'contactform': function(link) {
$scope.page.curlink = link;
$scope.page.curlink.mailtemplate = $scope.mailTemplates[$scope.mailTemplates.length - 1];
$scope.fnModal('linkbuilding/modal/contactform')
},
'getmailbody': function(link) {
var text = link.mailtemplate.mails[0].body;
// strip tags
text = text.replace(/(<([^>]+)>)/ig, "");
//replace
text = text.replace(/\[partner_link\]/g, link.partner.url);
text = text.replace(/\[partner_domain\]/g, link.partner.domain.domain);
console.log(text);
return text;
},
'checkstatus': function(link) {
link.inprogress = true;
$http.get("/rest/linkbuilding/link.checkstatus", {
'params': { websiteid: h.siteId(), '_id': link['_id']['$oid'] }
}).then(function successCallback(res) {
link.inprogress = false;
link.my = res.data.my
});
},
'delete': function(link) {
$http.post("/rest/linkbuilding/link.delete",
fnSingle.getParams(link)
).then(function successCallback(res) {
fnLinks.list();
});
}
}
// multiple link actions
var fnMultiple = {
getParams: function() {
var ids = [];
for (var i in $scope.links) {
if ($scope.links[i].checked) {
ids.push($scope.links[i]._id.$oid)
}
}
return {
'websiteid': h.siteId(),
'ids': ids,
}
},
multioptions: function() {
$scope.page.multioptions = false;
for (var i in $scope.links) {
if ($scope.links[i].checked) {
$scope.page.multioptions = true;
break
}
}
},
check: function(array) {
var event = array[0];
var link = array[1];
if (event.ctrlKey || event.shiftKey) {
var forceselect = false;
var _links = $filter('filter')($scope.links, $scope.page.filter);
// var _links = $scope.links;
for (var _link in _links) {
// curlink zetten
var curlink = _links[_link];
// eingen link gevonden
if (forceselect == true) {
if (curlink == link) {
forceselect = false;
break;
}
if (curlink.checked == true) {
forceselect = false;
break;
}
curlink.checked = true;
}
// 1e selectie = true
if (forceselect == false) {
if (curlink.checked == true) {
forceselect = true;
}
if (curlink == link) {
forceselect = true;
}
}
}
}
link.checked = !link.checked;
fnMultiple.multioptions();
},
checkall: function() {
$scope.page.checkall = !$scope.page.checkall;
var _links = $filter('filter')($scope.links, $scope.page.filter);
for (var i in _links) {
_links[i].checked = $scope.page.checkall;
}
fnMultiple.multioptions();
},
quickselect: function(m) {
for (var i in $scope.links) {
var bool = true
var t = false
if ($scope.page.quickselect.email) {
t = true;
bool = ($scope.links[i].partner.domain.email) ? bool : false;
}
if ($scope.page.quickselect.contactform) {
t = true;
bool = ($scope.links[i].partner.domain.contactform) ? bool : false;
}
if ($scope.page.quickselect.nocontact) {
t = true;
bool = ($scope.links[i].partner.domain.email || $scope.links[i].partner.domain.contactform) ? false : bool;
}
if (t) {
$scope.links[i].checked = bool;
}
}
fnMultiple.multioptions();
},
_findallstatus: function() {
for (var i in $scope.links) {
if ($scope.links[i].checked) {
$http.get("/rest/linkbuilding/link.checkstatus", {
'params': { websiteid: h.siteId(), '_id': $scope.links[i]['_id']['$oid'] }
}).then(function successCallback(res) {
$scope.links[i].inprogress = false;
$scope.links[i].checked = false;
fnMultiple._findallstatus();
});
break;
}
}
},
findallstatus: function() {
for (var i in $scope.links) {
if ($scope.links[i].checked) {
$scope.links[i].inprogress = true;
}
}
fnMultiple._findallstatus();
},
_findallcontact: function() {
for (var i in $scope.links) {
if ($scope.links[i].checked) {
$http.get("/rest/linkbuilding/link.findcontact", {
'params': { websiteid: h.siteId(), '_id': $scope.links[i]['_id']['$oid'] }
}).then(function successCallback(res) {
$scope.links[i] = res.data;
var date = new Date($scope.links[i].date * 1);
$scope.links[i].monthstring = date.toLocaleString('default', { year: 'numeric', 'month': 'long' })
$scope.links[i].date = $scope.page.kdates[date.toString()];
$scope.links[i].inprogress = false;
$scope.links[i].checked = false;
fnMultiple._findallcontact();
});
break;
}
}
},
findallcontact: function() {
for (var i in $scope.links) {
if ($scope.links[i].checked) {
$scope.links[i].inprogress = true;
}
}
fnMultiple._findallcontact();
},
'status': function() {
$http.post("/rest/linkbuilding/link.edit",
angular.extend({ 'fields': ['my.status'] },
fnMultiple.getParams(),
$scope.page.multiplelinks)
).then(function successCallback(res) {
fnLinks.list();
})
},
'date': function() {
$http.post("/rest/linkbuilding/link.edit",
angular.extend({ 'fields': ['date'] },
fnMultiple.getParams(),
$scope.page.multiplelinks)
).then(function successCallback(res) {
fnLinks.list();
})
},
'tag': function() {
$http.post("/rest/linkbuilding/link.edit",
angular.extend({ 'fields': ['my.tag'] },
fnMultiple.getParams(),
$scope.page.multiplelinks)
).then(function successCallback(res) {
fnLinks.list();
})
},
pre_edit: function(link) {
},
edit: function(link) {
$http.post("/rest/rank-tracker/edit",
angular.extend({
'my.status': $scope.page.multiplelinks.status,
'fields': ['my.status']
}, fnMultiple.getParams(link))).then(function successCallback(res) {
fnLinks.list();
})
},
pre_mail: function() {
$scope.page.multiplelinks.mail = { key: $scope.mailTemplates[0].key }
$scope.fnModal('linkbuilding/modal/multiple.mail')
},
'mail': function(link) {
$http.post("/rest/linkbuilding/link.mail",
angular.extend($scope.page.multiplelinks, fnMultiple.getParams())
).then(function successCallback(res) {
fnLinks.list();
})
},
delete: function(link) {
$http.post("/rest/linkbuilding/link.delete", fnMultiple.getParams(link)).then(function successCallback(res) { fnLinks.list(); })
}
}
$scope.fnExportAll = function() {
var text = '';
var out = '';
for (var _link in $scope.links) {
var curlink = $scope.links[_link];
text = text + curlink.url + "\r\n";
out += $filter('date')(new Date(curlink.d), 'dd-MM-yyyy') + ';'
out += curlink.partner.url+';'
if(curlink.my.url){
out += curlink.my.url+';'
} else {
out += ';'
}
if(curlink.my.url){
out += curlink.my.anchor + ';'
} else {
out += ';'
}
out += curlink.my.status + ';'
out += "\n";
}
var element = document.createElement('a');
element.setAttribute('href', 'data:text/html;,' + encodeURIComponent(out));
element.setAttribute('download', 'linkbuilding_export.csv');
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
// list links
fnLinks.list();
fnTemplates.list();
}
]);
app.controller('linkbuildingtemplate', ['$scope', '$timeout', '$http', '$rootScope', '$filter', 'h',
function($scope, $timeout, $http, $rootScope, $filter, h) {
//haal Template templates op
var getTemplates = function() {
$http.get('/rest/linkbuilding/template.list', {
params: {
'websiteid': h.siteId(),
}
}).then(function successCallback(res) {
$scope.mailTemplates = res.data;
});
};
getTemplates();
$scope.curTemplate = { workflow: [] };
$scope.fnNewTemplate = function() {
$scope.hideTemplateoptions = true;
$scope.templatetab = 1
$scope.curTemplate = { method: 'manual', provider:"", mails: [{ 'type': 'primary' }] };
}
$scope.fnRemoveFollowUp = function(index) {
$scope.curTemplate.mails.splice(index, 1);
}
$scope.fnAddFollowUp = function() {
$scope.curTemplate.mails.push({ method: 'manual', 'type': 'followup' });
}
$scope.fnCloseTemplate = function(curTemplate) {
$scope.hideTemplateoptions = false;
$scope.templatetab = 0
}
$scope.fnAddWorkFlow = function() {
$scope.curTemplate.workflow.push({ persoon: USER.UserId + "" });
}
$scope.fnRemoveWorkFlow = function(index) {
$scope.curTemplate.workflow.splice(index, 1);
}
$scope.fnAddTemplate = function() {
$http.post('/rest/linkbuilding/template.add', { 'websiteid': h.siteId(), 'template': $scope.curTemplate }).then(function successCallback(res) {
getTemplates();
$scope.templatetab = 0;
$scope.hideTemplateoptions = false;
});
}
$scope.fnShowEditTemplate = function(mailTemplate) {
$scope.templatetab = 1
$scope.curTemplate = mailTemplate;
}
$scope.fnDeleteTemplate = function(mailTemplate) {
$http.post('/rest/linkbuilding/template.delete', { 'websiteid': h.siteId(), 'template': mailTemplate}).then(function successCallback(res) {
getTemplates();
$scope.templatetab = 0;
$scope.hideTemplateoptions = false;
});
}
}
]);
app.controller('user', ['$scope', '$rootScope', '$http', '$location', '$translate', 'h',
function($scope, $rootScope, $http, $location, $translate, h) {
$scope.curUser = {};
$scope.page = {};
var _cb = {
loginfailed: function() {
$scope.page.message = { 'key': 'user.global.message_login_failed', class: 'text-warning' }
},
loginok: function(res) {
$rootScope.user = res.user;
//console.log($rootScope.user);
$location.path('/');
},
registrationnotvalid: function() {
$scope.page.message = { 'key': 'user.global.message_registration_failed', class: 'text-warning' }
},
registrationexists: function() {
$scope.page.message = { 'key': 'user.global.message_login_exists', class: 'text-warning' }
},
registrationok: function(res) {
$rootScope.user = res.user;
$scope.fnGoto('/sites/first');
//h.login($location.path('/sites/first'));
},
recoverfailed: function() {
$scope.page.message = { 'key': 'user.global.message_recover_failed', class: 'text-warning' }
},
recoverok: function() {
$scope.page.message = { 'key': 'user.global.message_recover_ok', class: 'text-warning' }
},
}
$scope.checklogin = function() {
if ($rootScope.user._id) {
$location.path('/');
}
if (!$rootScope.user.reseller.config.own && $location.path() == '/user/register') {
$location.path('/user/login');
}
}
$scope.userdetails = {
init: function() {
$http.get('/rest/user/user.get').then(function successCallback(res) {
$scope.curUser = res.data;
});
},
'set': function() {
$http.post('/rest/user/user.set', $scope.curUser);
},
'invoice': function(key) {
$scope.fnGoto('https://robinssl.seoserver.nl/invoice/marketingtracerclient.php?_id=' + $rootScope.user._id + '&key=' + key)
}
}
$scope.fnLogin = function() {
$http.post('/rest/user/login', $scope.curUser).then(function successCallback(res) {
_cb[res.data.cb](res.data)
});
}
$scope.fnRegister = function() {
$http.post('/rest/user/register', $scope.curUser).then(function successCallback(res) {
_cb[res.data.cb](res.data)
});
}
$scope.fnRecover = function() {
$http.post('/rest/user/recover', $scope.curUser).then(function successCallback(res) {
_cb[res.data.cb](res.data)
});
}
}
]);
app.controller('sites', ['$scope', '$rootScope', '$http', '$location', 'h',
function($scope, $rootScope, $http, $location, h) {
$scope.rand = function(min,max){
return Math.floor(Math.random() * (max - min + 1) + min)
}
$scope.bShowAdminDelete = function(site) {
return (site.status.expire > (Date.now() / 1000)) ? false : true;
}
$scope.page = {
pending: false,
orderstate: 'init'
};
if ($scope.orderstate) {
$scope.page.orderstate = $scope.orderstate;
}
$scope.curSite = {};
$scope.do = function(obj, method, param) {
if (obj == 'fnSite') {
fnSite[method](param);
}
}
var fnSite = {
'wemessage': function() {
$scope.fnGoto('/rest/pay/wemessage.start')
},
'list': function() {
$http.get('/rest/sites/list').then(function successCallback(res) {
$scope.sites = res.data;
});
},
'add': function() {
$http.post('/rest/sites/add', $scope.curSite).then(function successCallback(res) {
_cb[res.data.cb](res.data)
});
},
'adminupgrade': function() {
$http.post('/rest/sites/adminupgrade', { websiteid: h.siteId() })
},
'unpark': function() {
$http.post('/rest/sites/adminunpark', { websiteid: h.siteId() }).then(function successCallback(res) {
h.login(function() { $scope.fnClick('/') });
});
},
'admindelete': function(site) {
$http.post('/rest/sites/remove', site).then(function successCallback(res) {
side.hidden = true;
});
},
'delete': function(site) {
$http.post('/rest/sites/remove', $scope.curSite).then(function successCallback(res) {
$scope.curSite = {};
h.login(function() {});
fnSite.list();
});
},
'pre_edit': function(site) {
$http.get('/rest/locale/language.list').then(function successCallback(res) {
$scope.languages = res.data;
});
$http.get('/rest/locale/country.list').then(function successCallback(res) {
$scope.countries = res.data;
});
$scope.curSite = site;
$scope.fnModal('sites/modal/edit');
},
'edit': function(site) {
$http.post('/rest/sites/edit', site).then(function successCallback(res) {
});
},
'order_options': function() {
$http.get('/rest/pay/options.list').then(function successCallback(res) {
$scope.options = res.data;
var index = ($rootScope.user.site.locale.lang == 'nl') ? 0 : 1;
$scope.order = { 'currency': $scope.options.currency[index], 'method': $scope.options.methods[index] };
});
},
'pre_order': function(package) {
$http.post('/rest/sites/edit', site).then(function successCallback(res) {
});
},
}
var _cb = {
domainvalid: function(site) {
h.login(function() {
$location.path('/');
});
},
domaininvalid: function() {
$scope.page.message = { html: 'The domain is invalid', class: 'text-warning' }
},
domainduplicate: function() {
$scope.page.message = { html: 'This domain is not available for trials versions.', class: 'text-warning' }
},
deletefailed: function() {
$scope.page.message = { html: 'Registration failed, the user with email adress ' + $scope.curUser.email + ' allready exists. Did you mean to ', class: 'text-danger' }
},
}
}
]);
app.controller('site', ['$scope', '$timeout', '$http', '$rootScope', '$location', '$anchorScroll', '$filter', 'h',
function($scope, $timeout, $http, $rootScope, $location, $anchorScroll, $filter, h) {
$scope.page = {
newteammember: {},
newmenu: {}
};
$scope.roles = [
{ 'role': 4, 'name': 'owner' },
{ 'role': 5, 'name': 'normal user' },
{ 'role': 6, 'name': 'guest' },
]
$scope.do = function(obj, method, param) {
if (obj == 'fnSite') {
fnSite[method](param);
} else if (obj == 'fnPage') {
fnPage[method](param);
}
}
$http.get('/rest/locale/language.list').then(function successCallback(res) {
$scope.languages = res.data;
});
$http.get('/rest/locale/country.list').then(function successCallback(res) {
$scope.countries = res.data;
});
var fnSite = {
get: function() {
$http.get('/rest/sites/get', {
params: {
'websiteid': h.siteId(),
}
}).then(function successCallback(res) {
if (res.data._id) {
$scope.page.tab = 'edit';
$scope.user.site.users = res.data.users;
$scope.user.site.names = res.data.names;
$scope.user.site.api = res.data.api;
} else {
$scope.page.tab = 'intro';
}
});
},
'searchcity': function(q) {
$http.get('/rest/locale/city.list', { 'params': { 'q': q } }).then(function successCallback(res) {
$scope.cities = res.data;
});
},
'setcity': function(city) {
$scope.user.site.locale.city = city.city;
$scope.user.site.locale.uule = city.uule;
$scope.cities = [];
},
'add': function() {
$http.post('/rest/sites/teammember.add',
angular.extend({ 'websiteid': h.siteId() }, $scope.page.newteammember)
).then(function successCallback(res) {
$scope.page.newteammember = {};
fnSite.get();
});
},
'park': function() {
$http.post('/rest/sites/adminpark', { websiteid: h.siteId() }).then(function successCallback(res) {
h.login(function() { $scope.fnClick('/') });
});
},
'delete': function(teammember) {
$http.post('/rest/sites/teammember.delete',
angular.extend({ 'websiteid': h.siteId() }, teammember)
).then(function successCallback(res) {
fnSite.get();
});
},
'update': function(reseller) {
$http.post('/rest/sites/teammembers.update',
angular.extend({ 'websiteid': h.siteId() }, $scope.user.site)
).then(function successCallback(res) {
fnSite.get();
});
},
'edit': function(site) {
if (!$scope.user.site.locale.city || !$scope.user.site.locale.uule) {
$scope.user.site.locale.uule = '';
$scope.user.site.locale.city = '';
}
$http.post('/rest/sites/edit',
angular.extend({ 'websiteid': h.siteId() }, $scope.user.site)
).then(function successCallback(res) {
});
},
'addmenu': function() {
$scope.user.site.menu = $scope.user.site.menu || [];
$scope.user.site.menu.push($scope.page.newmenu);
fnSite.edit($scope.user.site);
},
'removemenu': function(index) {
$scope.user.site.menu.splice(index, 1);
fnSite.edit($scope.user.site);
},
}
fnSite.get();
}
]);
app.controller('contentlist', ['$scope', '$http', '$rootScope', '$filter', '$location', 'h', '$timeout',
function($scope, $http, $rootScope, $filter, $location, h, $timeout) {
$scope.page = { 'newmodel': { 'lang': $rootScope.user.site.locale.lang } };
var content = {
listModels: function() {
$http.get('/rest/content/model.list', {
params: {
websiteid: h.siteId(),
}
}).then(function successCallback(res) {
$scope.models = res.data.data;
content.checkProgress();
});
},
addModel: function(model) {
$http.post('/rest/content/model.add', angular.extend({ websiteid: h.siteId() }, model)).then(function successCallback(res) {
$scope.page.newmodel = {}
content.listModels();
});
},
createModel: function(model) {
$http.post('/rest/content/model.create', angular.extend({ websiteid: h.siteId() }, model)).then(function successCallback(res) {
model.status = 'checking';
});
},
deleteModel: function(model) {
$http.post('/rest/content/model.delete', angular.extend({ websiteid: h.siteId() }, model)).then(function successCallback(res) {
content.listModels();
});
},
checkProgress: function() {
var l = false;
for (var i in $scope.models) {
if ($scope.models[i].status == 'pending') {
l = true;
}
}
if (l) {
$timeout(function() {
content.listModels()
}, 5000);
}
}
}
$scope.do = function(method, param) {
content[method](param);
}
content.listModels()
}
]);
app.directive('wrapInTag', function () {
return {
restrict: 'A',
link: function (scope, elm, attr) {
console.log(attr);
var tag = attr.tag || 'strong';
var words = eval(attr.words) || [];
var text = attr.text;
for (var i = 0; i < words.length; i++) {
text = text.replace(words[i], '<' + tag + '>' + words[i] + '' + tag + '>');
}
elm.html(text);
}
}
});
app.controller('contentmodel', ['$scope', '$http', '$rootScope', '$filter', '$location', 'h', '$timeout',
function ($scope, $http, $rootScope, $filter, $location, h, $timeout) {
$scope.page = {
states: {}, 'tab': 'research', 'type': 'article', 'tones': {
"informative": true,
"persuasive": false,
"emotional": false,
"witty": true,
"funny": false,
"professional": true,
"informal": false
}
};
$scope.mypage = {};
$scope.page.states.explore = { 'open': false, 'cluster': 1 }
var model = {
getModel: function () {
$http.get('/rest/content/model.get', {
params: {
'websiteid': h.siteId(),
'modelid': $scope.modelid
}
}).then(function successCallback(res) {
$scope.q = res.data.q;
$scope.clusters = res.data.clusters;
$scope.dna = res.data.dna;
$scope.wordfreq = res.data.wordfreq;
$scope.trans = res.data.trans;
$scope.r = res.data.r;
$scope.lsi = res.data.lsi;
$scope.target = res.data.target;
$scope.pages = res.data.pages;
$scope.write = res.data.write;
$scope.x = {};
for (var i in $scope.wordfreq) {
$scope.x[$scope.wordfreq[i].word] = $scope.wordfreq[i].avg;
$scope.wordfreq[i].maxminavg = $scope.wordfreq[i].max - $scope.wordfreq[i].avg
}
charts.tfchart();
charts.dnaChart();
model.getMyPage();
});
},
setMyPage: function () {
$scope.mypage.pending = true;
$http.post('/rest/content/model.setmypage', angular.extend({ websiteid: h.siteId(), 'modelid': $scope.modelid }, $scope.mypage)).then(function successCallback(res) {
model.getMyPage()
});
},
writestatus: function () {
var l = false;
$http.get('/rest/content/model.get', {
params: {
'websiteid': h.siteId(),
'modelid': $scope.modelid
}
}).then(function successCallback(res) {
if (res.data.write.status == 'pending') {
l = true;
}
if (l) {
$timeout(function () {
model.writestatus();
}, 5000);
} else {
model.getMyPage();
$scope.write = res.data.write;
}
});
},
write: function () {
$scope.write = $scope.write || {};
$scope.write.status = 'pending';
$scope.page.tab = 'write';
$http.post('/rest/content/model.pre_write', angular.extend({ websiteid: h.siteId(), 'modelid': $scope.modelid }, { 'type': $scope.page.type, 'tones': $scope.page.tones })).then(function successCallback(res) {
model.writestatus();
// need to set up checks here!
});
},
getMyPage: function () {
$scope.mypage.pending = true;
$http.get('/rest/content/model.getmypage', {
params: {
'websiteid': h.siteId(),
'modelid': $scope.modelid
}
}).then(function successCallback(res) {
$scope.mypage = res.data;
if (res.data.mna) {
for (var i in $scope.wordfreq) {
var word = $scope.wordfreq[i].word;
$scope.wordfreq[i].my = res.data.counter[word] || 0;
}
$scope.mna = res.data.mna;
charts.tfchart();
charts.dnaChart();
$scope.mypage.pending = false;
model.getSpamandSuggestions();
$scope.page.tab = 'write';
}
});
},
getSpamandSuggestions: function () {
$scope.kwspam = [];
$scope.kwsuggestions = [];
for (var i in $scope.wordfreq) {
var f = $scope.wordfreq[i];
if (f.my > f.max) {
$scope.kwspam.push({ 'word': f.word, 'my': f.my, 'max': f.max });
}
if (f.my < f.avg) {
$scope.kwsuggestions.push({ 'word': f.word, 'my': f.my, 'avg': f.avg });
}
}
},
exploreCluster(cluster) {
lines = [];
$scope.page.states.explore = { 'open': true, 'cluster': cluster }
for (var i in $scope.clusters) {
for (var y in $scope.clusters[i]['lines']) {
var line = $scope.clusters[i]['lines'][y]['text'];
var src = $scope.clusters[i]['lines'][y]['src'];
var fit = $scope.clusters[i]['lines'][y]['dna'][cluster];
if ($scope.clusters[i]['lines'][y]['text'].length > 133) {
lines.push({ 'line': line, 'fit': fit, 'src': src, 'wordscore': $scope.clusters[i]['lines'][y]['wordscore'], 'score': $scope.clusters[i]['lines'][y]['score'] });
}
}
}
$scope.page.lines = lines;
},
exploreWord: function (word) {
$scope.page.states.explore.open = false;
var s = word.split(' ');
var boldarray = [];
for (var i in s) {
for (var w in $scope.trans[s[i]]) {
boldarray.push(w)
}
}
suggestions = [];
for (var i in $scope.clusters) {
for (var y in $scope.clusters[i]['lines']) {
var sent = $scope.clusters[i]['lines'][y];
if (sent.clean.indexOf(' ' + word + ' ') !== -1) {
suggestions.push(sent)
$scope.page.states.exploreword = { 'open': true, 'word': word, 'boldarray': boldarray, 'suggestions': suggestions }
}
}
}
}
}
model.getModel();
$scope.do = function (method, param) {
model[method](param);
}
var charts = {
tfchart: function () {
var d = angular.copy($filter('orderBy')($scope.wordfreq, 'avg', true));
var d = d.map(function (c) {
c.word = ($scope.trans[c.word]) ? Object.keys($scope.trans[c.word])[0] : c.word;
return c;
})
$scope.tfidfchart = {
data: d,
type: "serial",
//autoMarginOffset: 20,
marginLeft: 16,
categoryField: "word",
legend: {
enabled: false
},
chartScrollbar: {
"enabled": true,
"hideResizeGrips": true,
"offset": 10,
"scrollbarHeight": 10
},
categoryAxis: {
gridPosition: "start",
parseDates: false,
gridThickness: 0,
"labelRotation": 45
},
valueAxes: [{
"stackType": "regular",
dashLength: 4,
color: '#dedede',
position: "top",
axisAlpha: 0,
labelsEnabled: false
}],
graphs: [{
balloonText: "AVG distribution top 10 result
[[value]]",
type: "column",
title: "Keyword value",
valueField: "avg",
fillAlphas: 1,
lineColor: '#28a745',
}, {
balloonText: "Max distribution top 10 result
[[value]]",
type: "column",
title: "Keyword value",
valueField: "maxminavg",
fillAlphas: 1,
lineColor: '#dddddd',
}, {
balloonText: "My distribution
[[value]]",
"bullet": "round",
"bulletBorderAlpha": 1,
"bulletColor": "#FFFFFF",
"useLineColorForBulletBorder": true,
"fillAlphas": 0,
"lineThickness": 2,
"lineAlpha": 1,
"bulletSize": 7,
"valueField": "my",
lineColor: '#ffc107',
}],
zoom: [0, 18]
}
},
dnaChart: function () {
var dna = [];
var mna = [];
var ordered_clusters = $filter('orderBy')($scope.clusters, 'score', true);
for (var i in ordered_clusters) {
if ($scope.mna) {
$scope.max_fit = (Math.max(...$scope.dna), Math.max(...$scope.mna))
dna.push({ 'cluster': ordered_clusters[i].cluster, 'dna': $scope.dna[ordered_clusters[i].cluster] * 100, 'mna': $scope.mna[ordered_clusters[i].cluster] * 100, 'i': (i * 1) + 1 })
} else {
$scope.max_fit = Math.max(...$scope.dna);
dna.push({ 'cluster': ordered_clusters[i].cluster, 'dna': $scope.dna[ordered_clusters[i].cluster] * 100, 'i': (i * 1) + 1 })
}
}
$scope.dnachart = {
"type": "radar",
data: dna,
"theme": "none",
"valueAxes": [{
"labelsEnabled": false,
"axisTitleOffset": 14,
"minimum": 0,
"axisAlpha": 0.15
}],
"startDuration": 2,
"graphs": [{
"balloonText": "Cluster [[i]]
Goal: [[value]]%",
"bullet": "round",
"lineThickness": 2,
"fillAlphas": 0.8,
"valueField": "dna",
lineColor: '#28a745',
}, {
"balloonText": "Cluster [[i]]
My page: [[value]]%",
"bullet": "round",
"color": "#dedede",
"fillAlphas": 0.3,
"lineThickness": 2,
"valueField": "mna",
}],
"categoryField": "i",
"export": {
"enabled": true
}
};
},
}
$scope.untrans = function (w) {
var s = w.split(' ');
for (var i in s) {
s[i] = Object.keys($scope.trans[s[i]])[0];
}
return s.join(' ');
}
}
]);
app.controller('report', ['$scope', '$timeout', '$http', '$rootScope', '$controller', '$window', 'h',
function($scope, $timeout, $http, $rootScope, $controller, $window, h) {
// nieuw of huidig rapport
$scope.page = { 'newreport': { show: false }, dates: [], kdates: {},'simpleview':true };
var d = new Date();
var thismonth = d.getMonth() + 1;
var thismonth = new Date().getMonth() + 3;
for (var i = 0; i < 15; i++) {
var date = new Date(new Date().getFullYear(), thismonth, 0);
$scope.page.dates.push({ 'date': date });
$scope.page.kdates[date.toString()] = date;
thismonth = thismonth - 1;
}
$scope.do = function(obj, method, param) {
if (obj == 'fnReport') {
fnReport[method](param);
}
}
var fnReport = {
list: function() {
$http.get('/rest/report/report.list', {
params: {
websiteid: h.siteId(),
}
}).then(function successCallback(res) {
$scope.reports = res.data;
angular.forEach(res.data, function(k, v) {
var date = new Date(k.download.date * 1);
k.download.date = $scope.page.kdates[date.toString()];
k.download = k.download || { status: '' }
if (k.download.status == 'pending') {
$timeout(function() {
fnReport.list();
}, 10000);
}
})
});
},
'add': function(report) {
$http.post('/rest/report/report.add',
angular.extend({ websiteid: h.siteId() }, report)
).then(function successCallback(res) {
fnReport.list();
});
},
create: function(report) {
$http.post('/rest/report/report.generate', angular.extend({ websiteid: h.siteId() }, report))
report.download.status = 'pending'
fnReport.update(report);
},
'update': function(report) {
report.download = report.download || {};
report.download.date = report.download.date || new Date();
report.download.date = Math.round(report.download.date.getTime() / 1000)
if(report.download.enddate){
if($scope.page.simpleview == true){
delete report.download.enddate
} else {
console.log(report.download.enddate);
report.download.enddate = report.download.enddate || new Date();
report.download.enddate = Math.round(report.download.enddate.getTime() / 1000)
}
}
$http.post('/rest/report/report.update',
angular.extend({ websiteid: h.siteId() }, report)
).then(function successCallback(res) {
fnReport.list();
});
},
'download': function(report) {
console.log(report);
$window.location.href = '/rest/report/report.download/?websiteid=' + h.siteId() + '&reportid=' + report.report_id;
},
'delete': function(report) {
$http.post('/rest/report/report.delete',
angular.extend({ websiteid: h.siteId() }, report)
).then(function successCallback(res) {
fnReport.list();
});
},
}
fnReport.list();
}
]);
app.controller('reportitem', ['$scope', '$timeout', '$http', '$rootScope', '$location', '$anchorScroll', '$filter', 'h',
function($scope, $timeout, $http, $rootScope, $location, $anchorScroll, $filter, h) {
$scope.do = function(obj, method, param) {
if (obj == 'fnReport') {
fnReport[method](param);
} else if (obj == 'fnPage') {
fnPage[method](param);
}
}
$scope.reportcategories = [{
'translate': 'menu.hub.searchperformance',
'types': [
{ 'translate': 'menu.hub.searchperformance', 'view': 'api/hub.html', 'init': { 'urlreport': 'v1', 'urlview': 'searchperformance' }},
]
},
/* {
'translate': 'menu.linkbuilding.linkbuilding',
'types': [
{ 'translate': 'menu.linkbuilding.assistant', 'view': 'linkbuilding/assistant.html', 'init': '' },
]
},*/
{
'translate': 'menu.onpage.onpage',
'types': [
{ 'translate': 'menu.onpage.overview', 'view': 'onpage/index.status.html', 'init': { 'viewrulelabel': 'all' } }
]
},
{
'translate': 'menu.analytics4.analytics',
'types': [
{ 'parent': 'menu.analytics.channels.channels', 'translate': 'menu.analytics.channels.all', 'view': 'api/analytics4.html', 'init': { 'urlreport': 'channels', 'urlview': 'all' } },
{ 'parent': 'menu.analytics.channels.channels', 'translate': 'menu.analytics.channels.organic', 'view': 'api/analytics4.html', 'init': { 'urlreport': 'channels', 'urlview': 'organic' } },
{ 'parent': 'menu.analytics.channels.channels', 'translate': 'menu.analytics.channels.paid', 'view': 'api/analytics4.html', 'init': { 'urlreport': 'channels', 'urlview': 'paid' } },
{ 'parent': 'menu.analytics.channels.channels', 'translate': 'menu.analytics.channels.display', 'view': 'api/analytics4.html', 'init': { 'urlreport': 'channels', 'urlview': 'display' } },
{ 'parent': 'menu.analytics.channels.channels', 'translate': 'menu.analytics.channels.social', 'view': 'api/analytics4.html', 'init': { 'urlreport': 'channels', 'urlview': 'social' } },
{ 'parent': 'menu.analytics.channels.channels', 'translate': 'menu.analytics.channels.referral', 'view': 'api/analytics4.html', 'init': { 'urlreport': 'channels', 'urlview': 'referral' } },
{ 'parent': 'menu.analytics.channels.channels', 'translate': 'menu.analytics.channels.email', 'view': 'api/analytics4.html', 'init': { 'urlreport': 'channels', 'urlview': 'email' } },
{ 'parent': 'menu.analytics.channels.channels', 'translate': 'menu.analytics.channels.other', 'view': 'api/analytics4.html', 'init': { 'urlreport': 'channels', 'urlview': 'other' } },
]
},
{
'translate': 'menu.searchconsole.searchconsole',
'types': [
{ 'translate': 'menu.searchconsole.keywords', 'view': 'api/searchconsole.html', 'init': { 'urlreport': 'campaigns', 'urlview': 'keywords' } },
{ 'translate': 'menu.searchconsole.pages', 'view': 'api/searchconsole.html', 'init': { 'urlreport': 'campaigns', 'urlview': 'pages' } },
{ 'translate': 'menu.searchconsole.country', 'view': 'api/searchconsole.html', 'init': { 'urlreport': 'campaigns', 'urlview': 'country' } },
{ 'translate': 'menu.searchconsole.device', 'view': 'api/searchconsole.html', 'init': { 'urlreport': 'campaigns', 'urlview': 'device' } },
]
},
{
'translate': 'menu.googleads.googleads',
'types': [
{ 'translate': 'menu.googleads.all', 'view': 'api/googlexads.html', 'init': { 'urlreport': 'campaigns', 'urlview': 'all' } },
{ 'translate': 'menu.googleads.adgroups', 'view': 'api/googlexads.html', 'init': { 'urlreport': 'campaigns', 'urlview': 'adgroups' } },
{ 'translate': 'menu.googleads.keywords', 'view': 'api/googlexads.html', 'init': { 'urlreport': 'campaigns', 'urlview': 'keywords' } },
{ 'translate': 'menu.googleads.ads', 'view': 'api/googlexads.html', 'init': { 'urlreport': 'campaigns', 'urlview': 'ads' } },
{ 'translate': 'menu.googleads.conversions', 'view': 'api/googlexads.html', 'init': { 'urlreport': 'campaigns', 'urlview': 'conversions' } },
]
},
{
'translate': 'menu.facebookads.facebookads',
'types': [
{ 'translate': 'menu.facebookads.all', 'view': 'api/facebookxads.html', 'init': { 'urlreport': 'campaigns', 'urlview': 'all' } },
{ 'translate': 'menu.facebookads.adsets', 'view': 'api/facebookxads.html', 'init': { 'urlreport': 'campaigns', 'urlview': 'adsets' } },
{ 'translate': 'menu.facebookads.ads', 'view': 'api/facebookxads.html', 'init': { 'urlreport': 'campaigns', 'urlview': 'ads' } },
{ 'translate': 'menu.facebookads.keywords', 'view': 'api/facebookxads.html', 'init': { 'urlreport': 'campaigns', 'urlview': 'keywords' } },
{ 'translate': 'menu.facebookads.age', 'view': 'api/facebookxads.html', 'init': { 'urlreport': 'campaigns', 'urlview': 'age' } },
{ 'translate': 'menu.facebookads.gender', 'view': 'api/facebookxads.html', 'init': { 'urlreport': 'campaigns', 'urlview': 'gender' } },
]
},
{
'translate': 'menu.facebookinsights.facebookinsights',
'types': [
{ 'translate': 'menu.facebookinsights.fans', 'view': 'api/facebookinsights.html', 'init': { 'urlreport': 'page', 'urlview': 'fans' } },
{ 'translate': 'menu.facebookinsights.engagements', 'view': 'api/facebookinsights.html', 'init': { 'urlreport': 'page', 'urlview': 'engagements' } },
{ 'translate': 'menu.facebookinsights.reach', 'view': 'api/facebookinsights.html', 'init': { 'urlreport': 'page', 'urlview': 'reach' } },
{ 'translate': 'menu.facebookinsights.posts', 'view': 'api/facebookinsights.html', 'init': { 'urlreport': 'page', 'urlview': 'posts' } }
]
},
{
'translate': 'menu.mybusiness.mybusiness',
'types': [
{ 'translate': 'menu.mybusiness.all', 'view': 'api/mybusiness.html', 'init': { 'urlreport': 'business', 'urlview': 'all' } },
{ 'translate': 'menu.mybusiness.reviews', 'view': 'api/mybusiness.html', 'init': { 'urlreport': 'business', 'urlview': 'reviews' } },
]
},
{
'translate': 'menu.planning',
'types': [
{ 'translate': 'menu.planning', 'view': 'planning/index.html', 'init': '' },
]
},
{
'translate': 'menu.linkbuilding',
'types': [
{ 'translate': 'menu.linkbuilding', 'view': 'linkbuilding/assistant.html', 'init': '' },
]
},
];
var fnPage = {
'new': function() {
return {};
},
'type': function(o) {
o.page.type = false;
$timeout(function() {
o.page.type = o.type;
o.page.changetype = false;
fnReport.update();
}, 500);
}
}
var fnReport = {
get: function() {
$http.get('/rest/report/report.get', {
params: {
websiteid: h.siteId(),
report_id: $scope.report_id,
}
}).then(function successCallback(res) {
console.log('ORIG DATE TS', res.data.download.date)
// Handle both seconds and milliseconds timestamps
var dateTs = res.data.download.date * 1;
if (dateTs < 10000000000) dateTs = dateTs * 1000; // Convert seconds to milliseconds
var date = new Date(dateTs);
if(res.data.download.enddate){
console.log('END DATE TS', res.data.download.enddate)
// Handle both seconds and milliseconds timestamps
var endDateTs = res.data.download.enddate * 1;
if (endDateTs < 10000000000) endDateTs = endDateTs * 1000;
var enddate = new Date(endDateTs);
var endts = new Date(enddate.getFullYear(), enddate.getMonth(), 0).getTime() / 1000;
} else {
var endts = new Date(date.getFullYear(), date.getMonth() + 1, 0).getTime() / 1000;
}
res.data.download.date = date;
$rootScope.reportdates = {
/* api */
'end': endts,
'start': new Date(date.getFullYear(), date.getMonth(), 1).getTime() / 1000,
/* planning */
'datestring': date.toLocaleString('default', { year: 'numeric', 'month': 'long' })
}
console.log('REPORTDATES', $rootScope.reportdates);
$scope.report = res.data;
});
},
getsettings: function() {
$http.get('/rest/report/settings.get', {
params: {
websiteid: h.siteId(),
report_id: $scope.report_id,
}
}).then(function successCallback(res) {
$scope.settings = res.data;
});
},
'addpage': function(index) {
console.log(index, 'is index');
$scope.report.pages = $scope.report.pages || [];
$scope.report.pages.splice(index, 0, fnPage.new());
$timeout(function() {
angular.element('html, body').animate({ scrollTop: angular.element('#page' + index).offset().top - 120 }, 400);
}, 200);
},
'deletepage': function(index) {
$scope.report.pages = $scope.report.pages || [];
$scope.report.pages.splice(index, 1);
fnReport.update();
},
'update': function(report) {
report = $scope.report;
report.download = report.download || {};
report.download.date = report.download.date || new Date();
report.download.date = Math.round(report.download.date.getTime() / 1000)
$http.post('/rest/report/report.update',
angular.extend({ websiteid: h.siteId() }, $scope.report)
).then(function successCallback(res) {
fnReport.get();
});
},
}
fnReport.get();
fnReport.getsettings();
}
]);
app.controller('reportpage', ['$scope', '$timeout', '$http', '$rootScope', '$controller', '$window', 'h',
function($scope, $timeout, $http, $rootScope, $controller, $window, h) {
}
]);
app.controller('coreapi', ['$scope', '$timeout', '$http', '$rootScope', '$filter', 'h',
function ($scope, $timeout, $http, $rootScope, $filter, h) {
$scope.charts = {};
$scope.progresschart = {};
$scope.viewfilters = {};
var colorindex = 0;
$scope.dateoptions = [
{ start: 'TODAY', end: 'TODAY', description: 'Today' },
{ start: 'YESTYERDAY', end: 'YESTYERDAY', description: 'Yesterday' },
{ start: '7DAYSAGO', end: 'TODAY', description: 'Past 7 days' },
{ start: '14DAYSAGO', end: 'TODAY', description: 'Past 14 days' },
{ start: '30DAYSAGO', end: 'TODAY', description: 'Past 30 days' },
{ start: '1YEARAGO', end: 'TODAY', description: 'Past year' },
{ start: 'THISMONTH', end: 'TODAY', description: 'This month' },
{ start: 'LASTMONTH', end: 'TODAY', description: 'Previous month' },
]
$scope.compareoptions = [
{ 'compare': false, 'comparedescription': 'Do not compare' },
{ 'compare': 'PERIOD', 'comparedescription': 'Compare with previous period' },
{ 'compare': 'YEAR', 'comparedescription': 'Compare with previous year' },
]
$scope.fnCustomDate = {
'init': function () {
var d = new Date()
d.setMonth(d.getMonth() - 1);
$scope.customdate = { 'start': d, 'end': new Date(), 'show': false }
},
'set': function () {
console.log('------------------- AHHHHH ');
$scope.customdate.show = false;
$rootScope.reportdates.start = Math.round($scope.customdate.start.getTime() / 1000)
$rootScope.reportdates.end = Math.round($scope.customdate.end.getTime() / 1000)
$rootScope.reportdates.description = 'Custom';
getReportData();
}
}
$scope.fnCustomDate.init();
$scope.table = {
'curpage': 0,
'itemsperpage': 20,
'offset': 0,
fnSetOffset: function () {
this.offset = this.itemsperpage * (this.curpage - 1);
}
}
if (!$rootScope.reportdates) {
$rootScope.reportdates = { 'start': '30DAYSAGO', 'end': 'TODAY', 'description': 'Past 30 days', 'compare': false, 'comparedescription': 'Do not compare' };
}
$scope.fnDateSelection = function (reportdates) {
$rootScope.reportdates.start = reportdates.start;
$rootScope.reportdates.end = reportdates.end;
$rootScope.reportdates.description = reportdates.description;
getReportData();
}
$scope.fnCompareSelection = function (reportdates) {
$rootScope.reportdates.compare = reportdates.compare;
$rootScope.reportdates.comparedescription = reportdates.comparedescription;
getReportData();
}
$scope.getCustomer = function () {
if ($rootScope.user.site.role < 6) {
$http.get('/rest/' + $scope.cfg.urlbase + '/getcustomer', {
params: {
action: 'getproperties',
websiteid: h.siteId(),
}
}).then(function successCallback(res) {
$scope.customers = res.data;
$scope.fnModal('api/modal/' + $scope.cfg.urlbase + '.selectcustomer');
});
}
}
$scope.setCustomer = function (data) {
$http.get('/rest/' + $scope.cfg.urlbase + '/setcustomer', {
params: {
websiteid: h.siteId(),
customer: data
}
}).then(function successCallback(res) {
getReportData();
});
}
$scope.useApiGrant = function (apigrant) {
$http.get('/rest/api-core/apigrants.set', {
params: {
websiteid: h.siteId(),
'type': $scope.cfg.urlbase,
'grantid': apigrant.grantid,
}
}).then(function successCallback(res) {
getReportData();
});
}
$scope.fnOptions = function () {
if ($scope.view.viewfilters) {
$http.get('/rest/' + $scope.cfg.urlbase + '/getviewfilters', {
params: {
websiteid: h.siteId(),
viewfilters: $scope.view.viewfilters.join(',')
}
}).then(function successCallback(res) {
$scope.viewfilteroptions = res.data.data
});
}
if ($scope.cfg.switchclient) {
$http.get('/rest/' + $scope.cfg.urlbase + '/getcustomer', {
params: {
action: 'getproperties',
websiteid: h.siteId(),
}
}).then(function successCallback(res) {
$scope.customers = res.data;
});
}
$scope.fnModal('api/modal/' + $scope.cfg.urlbase + '.options');
}
$scope.pullCustomer = function () {
$http.get('/rest/' + $scope.cfg.urlbase + '/pullcustomer', {
params: {
websiteid: h.siteId()
}
}).then(function successCallback(res) {
getReportData();
});
}
var _fnFetchData = function (i) {
if ($scope.view[i]) {
var vv = {};
var metrics = ($scope.view[i].metrics) ? $scope.view[i].metrics : $scope.metrics;
if ($scope.viewfilters) {
for (var y in $scope.viewfilters) {
vv[y] = $scope.viewfilters[y]['viewfilter'];
}
}
$http.get($scope.cfg.report_endpoint, {
params: {
websiteid: h.siteId(),
metrics: metrics.join(','),
dimension: $scope.view[i].dimension,
dimension2: $scope.view[i].dimension2,
report: $scope.view[i].report,
channel: $scope.view[i].channel,
context: $scope.view[i].context,
viewfilters: vv,
compare: $scope.view[i].compare || $rootScope.reportdates.compare,
allowcomp: $scope.view[i].allowcomp,
level: $scope.view[i].level,
endpoint: $scope.view[i].endpoint,
summary: $scope.view[i].summary,
breakdowns: $scope.view[i].breakdowns,
filterdimension: $scope.view[i].filterdimension,
filterexpression: $scope.view[i].filterexpression,
filteroperator: $scope.view[i].filteroperator,
sort: $scope.view[i].sort,
sortorder: $scope.view[i].sortorder,
start: ($scope.view[i].start) ? $scope.view[i].start : $rootScope.reportdates.start,
end: ($scope.view[i].end) ? $scope.view[i].end : $rootScope.reportdates.end,
limit: $scope.view[i].limit,
status: $scope.view[i].status,
campaignid: $scope.view[i].campaignid,
filter: $scope.view[i].filter,
campaign: $scope.view[i].campaign,
adgroup: $scope.view[i].adgroup,
network: $scope.view[i].network,
zeroimpressions: $scope.view[i].zeroimpressions,
}
}).then(function successCallback(res) {
if ($scope.view[i].callback) {
$scope.view[i].callback(res.data);
}
if (res.data.error) {
$scope.fnModal('api/modal/global.norights');
} else if (res.data.action == 'connect') {
$scope.fnModal('api/modal/' + $scope.cfg.urlbase + '.connect');
$http.get('/rest/api-core/apigrants.get', {
params: {
websiteid: h.siteId(),
'type': $scope.cfg.urlbase,
}
}).then(function successCallback(res) {
if (res.data.data !== null) {
$scope.apigrants = res.data.data;
}
});
if (res.data.sets) {
for (var set in res.data.sets) {
$scope.analytics[set] = res.data.sets[set];
for (var y in $scope.charts) {
if ($scope.charts[y].dataset == set) {
fnMakeChart($scope.charts[y]);
}
}
}
$scope.analytics[i] = { totals: res.data.totals };
} else {
$scope.analytics[i] = res.data;
for (var y in $scope.charts) {
if ($scope.charts[y].dataset == i) {
fnMakeChart($scope.charts[y]);
}
}
}
} else if (res.data.action == 'getcustomer') {
$scope.getCustomer();
} else if (res.data.sets) {
for (var set in res.data.sets) {
$scope.analytics[set] = res.data.sets[set];
for (var y in $scope.charts) {
if ($scope.charts[y].dataset == set) {
fnMakeChart($scope.charts[y]);
}
}
}
$scope.analytics[i] = { totals: res.data.totals };
} else {
$scope.analytics[i] = res.data;
for (var y in $scope.charts) {
if ($scope.charts[y].dataset == i) {
fnMakeChart($scope.charts[y]);
}
}
}
}, function errorCallback() {
$scope.fnModal('api/modal/global.error');
});
}
}
var getReportData = function () {
$scope.fnCloseModal();
$scope.analytics = {};
$scope.pagecharts = {};
_fnFetchData('first');
_fnFetchData('second');
_fnFetchData('third');
_fnFetchData('fourth');
}
var fnMakeChart = function (chart) {
console.log(chart.dataset);
var dataset = $scope.analytics[chart.dataset].data;
var dimension = (chart.dimension) ? chart.dimension : ($scope.cfg.dimensionprefix + $scope.view[chart.dataset].dimension);
var metric = (chart.metric) ? chart.metric : $scope.metric;
$scope.progresschart[chart.chart] = false;
if (chart['type'] == 'line') {
_charts.fnLineChart(chart, dataset, dimension, metric);
} else if (chart['type'] == 'bar') {
_charts.fnBarChart(chart, dataset, dimension, metric);
} else if (chart['type'] == 'pie') {
_charts.fnPieChart(chart, dataset, dimension, metric);
} else if (chart['type'] == 'linemulti') {
_charts.fnLineChartMultiple(chart, dataset, dimension, metric);
} else if (chart['type'] == 'bubble') {
_charts.fnBubbleChart(chart, dataset, dimension, metric);
} else if (chart['type'] == 'progress') {
_charts.fnProgressChart(chart, dataset, dimension, metric);
}
}
$scope.colors = ['#E91E63', '#3F51B5', '#00BCD4', '#8BC34A', '#FF9800', '#795548', '#607D8B', '#00CC00', '#0000CC', '#DDDDDD', '#999999', '#333333', '#990000']
var _charts = {
fnBubbleChart: function (chart, dataset, dimension, metric) {
return false;
},
fnProgressChart: function (chart, dataset, dimension, metric) {
$scope.pagecharts[chart.chart] = false
var b = $filter('orderBy')(dataset, metric, $scope.order.reverse).slice(0, 5);
$scope.progresschart[chart.chart] = b;
},
fnLineChart: function (chart, dataset, dimension, metric) {
$scope.gloMetrics[metric] = $scope.gloMetrics[metric] || {};
var datamerge = $scope.gloMetrics[metric].datemerge;
var datesubstr = ($scope.cfg.dateformat == 'YYYYMMDD') ? [0, 4, 4, 6, 6, 8] : [0, 4, 5, 7, 8, 10];
var data = {};
// if (dataset.length > 62 && datamerge) {
// for (var i in dataset) {
// // todo date format YYYY-MM-DD vs YYYYMMDD
// var year = dataset[i][dimension].toString().substring(datesubstr[0], datesubstr[1]);
// var month = dataset[i][dimension].toString().substring(datesubstr[2], datesubstr[3]);
// var day = dataset[i][dimension].toString().substring(datesubstr[4], datesubstr[5]);
// var date = new Date(year, month - 1, day);
// onejan = new Date(date.getFullYear(), 0, 1);
// week = Math.ceil((((date - onejan) / 86400000) + onejan.getDay() + 1) / 7);
// var yw = year + '_' + week;
// if (data[yw]) {
// data[yw][metric] += dataset[i][metric];
// data[yw]['n'] += 1;
// } else {
// data[yw] = dataset[i];
// data[yw]['n'] = 1;
// }
// }
// // todo calc average
// dataset = $filter('orderBy')(Object.values(data), 'dimension', false);
// if (datamerge == 'avg') {
// dataset = dataset.map(function (c) {
// c[metric] = c[metric] / c['n'];
// return c;
// })
// }
// }
// A new function to merge data into 31-day buckets, starting from the last entry.
if (dataset.length > 62 && datamerge) { // Condition changed to 31 for one bucket period.
// Assuming the dataset is sorted by date, with the most recent date at the end.
// If not sorted, you would need to find the maximum date first.
const lastEntry = dataset[dataset.length - 1];
const endDateYear = lastEntry[dimension].toString().substring(datesubstr[0], datesubstr[1]);
const endDateMonth = lastEntry[dimension].toString().substring(datesubstr[2], datesubstr[3]);
const endDateDay = lastEntry[dimension].toString().substring(datesubstr[4], datesubstr[5]);
// Set the anchor date to the end of the day of the last entry.
const anchorDate = new Date(endDateYear, endDateMonth - 1, endDateDay);
anchorDate.setHours(23, 59, 59, 999); // Use end of day to ensure the last day is included.
const data = {};
const MS_PER_DAY = 86400000; // Milliseconds in one day (24 * 60 * 60 * 1000)
let period = 7;
// Loop backwards from the end of the dataset.
for (let i = dataset.length - 1; i >= 0; i--) {
const item = dataset[i];
// Parse the date for the current item.
const year = item[dimension].toString().substring(datesubstr[0], datesubstr[1]);
const month = item[dimension].toString().substring(datesubstr[2], datesubstr[3]);
const day = item[dimension].toString().substring(datesubstr[4], datesubstr[5]);
const currentDate = new Date(year, month - 1, day);
// Calculate how many days ago this date was from our anchor date.
const daysDifference = (anchorDate.getTime() - currentDate.getTime()) / MS_PER_DAY;
// Determine the bucket index. Each bucket is a 31-day period.
// Math.floor(0 / 31) = 0, Math.floor(30.9 / 31) = 0, Math.floor(31 / 31) = 1
const bucketIndex = Math.floor(daysDifference / period);
// Create a representative date for the bucket's start day for a consistent key.
const bucketStartDate = new Date(anchorDate);
bucketStartDate.setDate(anchorDate.getDate() - (bucketIndex + 1) * period + 1);
// Format the bucket key to match the API's date format.
const bYear = bucketStartDate.getFullYear();
const bMonth = ('0' + (bucketStartDate.getMonth() + 1)).slice(-2);
const bDay = ('0' + bucketStartDate.getDate()).slice(-2);
const bucketKey = ($scope.cfg.dateformat === 'YYYYMMDD')
? '' + bYear + bMonth + bDay
: bYear + '-' + bMonth + '-' + bDay;
// Aggregate data into the bucket.
if (data[bucketKey]) {
data[bucketKey][metric] += item[metric];
data[bucketKey]['n'] += 1;
} else {
// Create a new entry for the bucket.
data[bucketKey] = { ...item }; // Copy item to avoid modifying original
data[bucketKey][dimension] = bucketKey; // Set the dimension to our bucket's start date
data[bucketKey]['n'] = 1;
}
}
// Convert the aggregated data object back to an array and sort it.
dataset = $filter('orderBy')(Object.values(data), dimension, false);
// If 'avg' is specified, calculate the average for each bucket.
if (datamerge === 'avg') {
dataset = dataset.map(function (c) {
c[metric] = c[metric] / c['n'];
return c;
});
}
}
$scope.pagecharts[chart.chart] = {
data: dataset,
type: 'serial',
theme: 'light',
marginLeft: 0,
marginRight: 0,
marginTop: 10,
fontFamily: 'Roboto',
categoryField: dimension,
rotate: false,
pathToImages: 'vendor/amchart/images/',
dataDateFormat: $scope.cfg.dateformat,
categoryAxis: {
gridPosition: "start",
parseDates: true,
gridThickness: 0
},
valueAxes: [{
position: "left",
axisAlpha: 0,
labelsEnabled: !chart.hideAxisLabels,
dashLength: 4,
color: '#dedede',
}],
graphs: [{
balloonText: $filter('translate')('metrics.' + metric) + "
[[value]]",
"bullet": "round",
"bulletBorderAlpha": 1,
"bulletColor": "#FFFFFF",
"useLineColorForBulletBorder": true,
"fillAlphas": 0.2,
"lineThickness": 2,
"lineAlpha": 1,
"bulletSize": 7,
"connect": true,
"type": "smoothedLine",
"title": "Expenses",
"valueField": metric,
lineColor: $scope.colors[colorindex],
}],
}
colorindex += 1;
},
fnLineChartMultiple: function (chart, dataset, dimension, metric) {
var d = [];
var dimension2 = (chart.dimension2) ? chart.dimension2 : $scope.cfg.dimensionprefix + $scope.view[chart.dataset].dimension2;
var datamerge = ($scope.gloMetrics[metric] || {}).datemerge;
if (dataset && dataset.length > 62 && datamerge) {
var datesubstr = ($scope.cfg.dateformat == 'YYYYMMDD') ? [0, 4, 4, 6, 6, 8] : [0, 4, 5, 7, 8, 10];
var lastEntry = dataset[dataset.length - 1];
if (lastEntry) {
var lastYear = lastEntry[dimension].toString().substring(datesubstr[0], datesubstr[1]);
var lastMonth = lastEntry[dimension].toString().substring(datesubstr[2], datesubstr[3]);
var lastDay = lastEntry[dimension].toString().substring(datesubstr[4], datesubstr[5]);
var anchorDate = new Date(lastYear, lastMonth - 1, lastDay);
anchorDate.setHours(23, 59, 59, 999);
var MS_PER_DAY = 86400000;
var period = 7;
var bucketMap = {};
var formatBucketKey = function (dateObj) {
var year = dateObj.getFullYear();
var month = ('0' + (dateObj.getMonth() + 1)).slice(-2);
var day = ('0' + dateObj.getDate()).slice(-2);
if ($scope.cfg.dateformat === 'YYYYMMDD') {
return '' + year + month + day;
}
return year + '-' + month + '-' + day;
};
for (var idx = dataset.length - 1; idx >= 0; idx--) {
var row = dataset[idx];
var keyYear = row[dimension].toString().substring(datesubstr[0], datesubstr[1]);
var keyMonth = row[dimension].toString().substring(datesubstr[2], datesubstr[3]);
var keyDay = row[dimension].toString().substring(datesubstr[4], datesubstr[5]);
var currentDate = new Date(keyYear, keyMonth - 1, keyDay);
var daysDifference = (anchorDate.getTime() - currentDate.getTime()) / MS_PER_DAY;
var bucketIndex = Math.floor(daysDifference / period);
var bucketStart = new Date(anchorDate);
bucketStart.setDate(anchorDate.getDate() - (bucketIndex + 1) * period + 1);
var bucketKey = formatBucketKey(bucketStart);
var segment = row[dimension2];
if (!segment) {
continue;
}
bucketMap[bucketKey] = bucketMap[bucketKey] || {};
bucketMap[bucketKey][segment] = bucketMap[bucketKey][segment] || { sum: 0, count: 0 };
bucketMap[bucketKey][segment].sum += parseFloat(row[metric]) || 0;
bucketMap[bucketKey][segment].count += 1;
}
var mergedDataset = [];
var bucketKeys = Object.keys(bucketMap).sort();
for (var kIndex = 0; kIndex < bucketKeys.length; kIndex++) {
var bucketKey = bucketKeys[kIndex];
var segments = bucketMap[bucketKey];
for (var segmentName in segments) {
if (!segments.hasOwnProperty(segmentName)) {
continue;
}
var entry = segments[segmentName];
var value = (datamerge === 'avg' && entry.count > 0) ? entry.sum / entry.count : entry.sum;
if (!isFinite(value)) {
value = 0;
}
var mergedRow = {};
mergedRow[dimension] = bucketKey;
mergedRow[dimension2] = segmentName;
mergedRow[metric] = value;
mergedDataset.push(mergedRow);
}
}
dataset = mergedDataset;
}
}
if (!chart.graphs || !chart.graphs.length) {
var totalsByDimension2 = {};
dataset.forEach(function (row) {
var groupKey = row[dimension2];
if (!groupKey) {
return;
}
var value = parseFloat(row[metric]);
if (isNaN(value)) {
return;
}
totalsByDimension2[groupKey] = (totalsByDimension2[groupKey] || 0) + value;
});
chart.graphs = Object.keys(totalsByDimension2)
.map(function (key) {
return { name: key, total: totalsByDimension2[key] };
})
.sort(function (a, b) { return b.total - a.total; })
.slice(0, 5)
.map(function (item) { return item.name; });
}
for (var i in dataset) {
var row = dataset[i];
d[row[dimension]] = d[row[dimension]] || {};
d[row[dimension]].c = row[dimension];
if (chart.graphs) {
for (var y in chart.graphs) {
if (chart.graphs[y] == row[dimension2]) {
d[row[dimension]]['t' + chart.graphs[y]] = row[metric];
}
}
}
}
console.log('chart', chart);
var b = [];
Object.keys(d).sort().forEach(function (key) {
b.push(d[key]);
});
var graphs = [];
for (var y in chart.graphs) {
// *** FIX: Changed 'var' to 'let' to correctly scope the variable for the closure ***
let segmentName = chart.graphs[y];
graphs.push({
balloonText: chart.graphs[y] + '
' + $filter('translate')('metrics.' + metric) + " [[value]]",
"bullet": "round",
"bulletBorderAlpha": 1,
"bulletColor": "#FFFFFF",
"useLineColorForBulletBorder": true,
"fillAlphas": 0,
"lineThickness": 2,
"lineAlpha": 1,
"bulletSize": 7,
"title": "Expenses",
"valueField": 't' + chart.graphs[y],
lineColor: $scope.colors[y],
stacked: false,
balloonFunction: function (item) {
var dateLabel = $filter('date')(item.category, 'yyyy-MM-dd');
var rawValue = item.values.value;
var formatted = $filter('useFilter')(
rawValue,
$scope.gloMetrics[metric].typefilter,
$scope.gloMetrics[metric].filtercomparator
);
var suffix = $scope.gloMetrics[metric].after || '';
return (
'' +
'
' + segmentName + '
' + // This will now use the correct segmentName
'
' + dateLabel + '
' +
'
' +
$filter('translate')('metrics.' + metric) +
': ' + formatted + suffix + '' +
'
' +
'
'
);
}
});
if (chart.graphs[y] == 'male') {
graphs[y].lineColor = '#3F51B5';
}
if (chart.graphs[y] == 'female') {
graphs[y].lineColor = '#E91E63';
}
}
$scope.pagecharts[chart.chart] = {
data: b,
type: 'serial',
theme: 'light',
marginLeft: 0,
marginRight: 0,
marginTop: 10,
fontFamily: 'Roboto',
categoryField: 'c',
rotate: false,
pathToImages: 'vendor/amchart/images/',
dataDateFormat: $scope.cfg.dateformat,
legend: {
enabled: false
},
categoryAxis: {
gridPosition: "start",
parseDates: true,
labelRotation: 45
},
valueAxes: [{
position: "left",
axisAlpha: 0,
labelsEnabled: true,
}],
graphs: graphs,
};
if (chart.stacked) {
$scope.pagecharts[chart.chart].valueAxes[0].stackType = "regular";
}
},
fnBarChart: function (chart, dataset, dimension, metric) {
//inject color attribute with value
if (typeof $scope.analytics[chart.dataset].data != undefined) {
for (var i = 0; i < $scope.analytics[chart.dataset].data.length; i++) { $scope.analytics[chart.dataset].data[i].color = $scope.colors[i]; }
}
$scope.pagecharts[chart.chart] = {
data: dataset.slice(0, 6),
type: 'serial',
theme: 'light',
marginLeft: 0,
marginRight: 0,
marginTop: 10,
fontFamily: 'Roboto',
categoryField: dimension,
rotate: false,
pathToImages: 'vendor/amchart/images/',
dataDateFormat: $scope.cfg.dateformat,
legend: {
enabled: false
},
categoryAxis: {
gridPosition: "start",
parseDates: false,
labelRotation: 0,
gridThickness: 0,
labelFunction: function (value, valueText, axis) { return (value.length > 19) ? value.substr(0, 17) + '...' : value },
},
valueAxes: [{
position: "left",
axisAlpha: 0,
labelsEnabled: true,
dashLength: 4,
color: '#dedede',
}],
graphs: [{
fillColorsField: "color",
balloonText: "[[category]]: [[value]]",
type: "column",
columnWidth: 0.40,
title: "Zoekwoord waarde",
valueField: metric,
fillAlphas: 1,
lineColor: '#009688',
autoColor: true,
}],
}
},
fnPieChart: function (chart, dataset, dimension, metric) {
$scope.pagecharts[chart.chart] = {
data: dataset.slice(0, 9),
type: 'pie',
theme: 'light',
marginLeft: 0,
marginRight: 0,
marginTop: 10,
fontFamily: 'Roboto',
categoryField: dimension,
pathToImages: 'vendor/amchart/images/',
"precision": 0,
"decimalSeparator": ",",
"thousandsSeparator": ".",
legend: {
enabled: true,
position: 'right',
autoMargins: false,
marginRight: 100,
},
titleField: dimension,
valueField: metric,
labelRadius: 5,
radius: '42%',
innerRadius: '60%',
labelText: '[[title]]',
}
}
}
$scope.fnAddChart = function (name, functionname) {
_charts[name] = functionname;
}
// Grafieken voor een metric. Heeft de view er geen eigen grafiek voor (veel views noemen alleen een paar
// metrics in view.charts), dan dezelfde grafieken als de standaardmetric van de view, met de naam van deze
// metric; een taart wordt een staaf bij een gemiddelde of percentage.
var fnChartsFor = function (metric) {
if (!$scope.view || !$scope.view.charts) {
return {};
}
if ($scope.view.charts[metric]) {
return $scope.view.charts[metric];
}
var base = $scope.view.charts[$scope.view.selectedmetric] || $scope.view.charts[Object.keys($scope.view.charts)[0]];
if (!Array.isArray(base)) {
return {};
}
var label = $filter('translate')('metrics.' + metric);
if (label === 'metrics.' + metric) {
label = (($scope.gloMetrics || {})[metric] || {}).name || metric;
}
var avg = ((($scope.gloMetrics || {})[metric]) || {}).datemerge === 'avg';
$scope.view.charts[metric] = base.map(function (chart) {
var copy = angular.extend({}, chart, {
name: chart.name === '_' ? '_' : label,
type: (chart.type === 'pie' && avg) ? 'bar' : chart.type
});
delete copy.metric;
return copy;
});
return $scope.view.charts[metric];
}
$scope.fnSwitchMetric = function (selectedmetric) {
$scope.metric = selectedmetric;
$scope.charts = fnChartsFor($scope.metric);
if ($scope.view && $scope.view.fullchart && Array.isArray($scope.charts)) {
var hasActive = false;
for (var idx = 0; idx < $scope.charts.length; idx++) {
if ($scope.charts[idx].active) {
hasActive = true;
break;
}
}
if (!hasActive && $scope.charts.length) {
$scope.charts[0].active = true;
}
}
var reverse = ($scope.gloMetrics[selectedmetric].reverse === true || $scope.gloMetrics[selectedmetric].reverse === false) ? $scope.gloMetrics[selectedmetric].reverse : true;
$scope.order = { order: selectedmetric, 'reverse': reverse };
for (var i in $scope.charts) {
fnMakeChart($scope.charts[i]);
}
}
$scope.fnActivateChart = function (chart) {
if (!$scope.charts || !chart) {
return;
}
angular.forEach($scope.charts, function (item) {
item.active = false;
});
chart.active = true;
fnMakeChart(chart);
}
$scope.fnConnect = function () {
window.location.replace($scope.cfg.authurl + '&h=/' + $scope.cfg.urlbase + '/' + $scope.urlreport + '/' + $scope.urlview);
}
$scope.getReportData = function () {
getReportData();
}
$scope.fnMakeChart = function (chart) {
fnMakeChart(chart);
}
$scope.fnPctChange = function (o, n) {
if (n == 0) {
return '0%';
}
var pct = ((n - o) / o) * 100;
var npct = $filter('number')(pct, 2);
if (pct < 0) {
return '' + npct + '%';
}
return '+ ' + npct + '%';
}
$scope.fnSaveSelectedMetrics = function () {
$http.post('/rest/api-core/selectedmetrics.set', {
websiteid: h.siteId(),
'type': $scope.cfg.urlbase,
'view': $scope.urlreport + '-' + $scope.urlview,
'selectedmetrics': $scope.selectedmetrics,
});
}
$scope.fnRemoveViewfilter = function (type) {
delete $scope.viewfilters[type];
getReportData();
}
$scope.init = function () {
if ($scope.custominit) {
$scope.urlreport = $scope.custominit.urlreport;
$scope.urlview = $scope.custominit.urlview;
}
/* set selection of view metric and chart*/
$scope.views = $scope.reportconfig[$scope.urlreport].views;
$scope.view = $scope.reportconfig[$scope.urlreport].views[$scope.urlview];
/* selected wmetrics ophalen ...... aparte functie ... ?*/
$scope.selectedmetrics = $scope.view.selectedmetrics;
$http.get('/rest/api-core/selectedmetrics.get', {
params: {
websiteid: h.siteId(),
'type': $scope.cfg.urlbase,
'view': $scope.urlreport + '-' + $scope.urlview,
}
}).then(function successCallback(res) {
if (res.data.data !== null) {
$scope.selectedmetrics = res.data.data;
}
});
$scope.metrics = $scope.view.metrics;
$scope.metric = $scope.view.selectedmetric;
$scope.order = { order: $scope.view.selectedmetric, reverse: true };
$scope.charts = fnChartsFor($scope.metric);
getReportData();
}
var l = {
"ab": "Abkhaz",
"aa": "Afar",
"af": "Afrikaans",
"ak": "Akan",
"sq": "Albanian",
"am": "Amharic",
"ar": "Arabic",
"an": "Aragonese",
"hy": "Armenian",
"as": "Assamese",
"av": "Avaric",
"ae": "Avestan",
"ay": "Aymara",
"az": "Azerbaijani",
"bm": "Bambara",
"ba": "Bashkir",
"eu": "Basque",
"be": "Belarusian",
"bn": "Bengali; Bangla",
"bh": "Bihari",
"bi": "Bislama",
"bs": "Bosnian",
"br": "Breton",
"bg": "Bulgarian",
"my": "Burmese",
"ca": "Catalan; Valencian",
"ch": "Chamorro",
"ce": "Chechen",
"ny": "Chichewa; Chewa; Nyanja",
"zh": "Chinese",
"cv": "Chuvash",
"kw": "Cornish",
"co": "Corsican",
"cr": "Cree",
"hr": "Croatian",
"cs": "Czech",
"da": "Danish",
"dv": "Divehi; Dhivehi; Maldivian;",
"nl": "Nederlands",
"dz": "Dzongkha",
"en": "English",
"eo": "Esperanto",
"et": "Estonian",
"ee": "Ewe",
"fo": "Faroese",
"fj": "Fijian",
"fi": "Finnish",
"fr": "French",
"ff": "Fula; Fulah; Pulaar; Pular",
"gl": "Galician",
"ka": "Georgian",
"de": "German",
"el": "Greek, Modern",
"gn": "GuaranÃ",
"gu": "Gujarati",
"ht": "Haitian; Haitian Creole",
"ha": "Hausa",
"he": "Hebrew (modern)",
"hz": "Herero",
"hi": "Hindi",
"ho": "Hiri Motu",
"hu": "Hungarian",
"ia": "Interlingua",
"id": "Indonesian",
"ie": "Interlingue",
"ga": "Irish",
"ig": "Igbo",
"ik": "Inupiaq",
"io": "Ido",
"is": "Icelandic",
"it": "Italian",
"iu": "Inuktitut",
"ja": "Japanese",
"jv": "Javanese",
"kl": "Kalaallisut, Greenlandic",
"kn": "Kannada",
"kr": "Kanuri",
"ks": "Kashmiri",
"kk": "Kazakh",
"km": "Khmer",
"ki": "Kikuyu, Gikuyu",
"rw": "Kinyarwanda",
"ky": "Kyrgyz",
"kv": "Komi",
"kg": "Kongo",
"ko": "Korean",
"ku": "Kurdish",
"kj": "Kwanyama, Kuanyama",
"la": "Latin",
"lb": "Luxembourgish, Letzeburgesch",
"lg": "Ganda",
"li": "Limburgish, Limburgan, Limburger",
"ln": "Lingala",
"lo": "Lao",
"lt": "Lithuanian",
"lu": "Luba-Katanga",
"lv": "Latvian",
"gv": "Manx",
"mk": "Macedonian",
"mg": "Malagasy",
"ms": "Malay",
"ml": "Malayalam",
"mt": "Maltese",
"mi": "MÄori",
"mr": "Marathi (MarÄá¹hÄ«)",
"mh": "Marshallese",
"mn": "Mongolian",
"na": "Nauru",
"nv": "Navajo, Navaho",
"nb": "Norwegian Bokmål",
"nd": "North Ndebele",
"ne": "Nepali",
"ng": "Ndonga",
"nn": "Norwegian Nynorsk",
"no": "Norwegian",
"ii": "Nuosu",
"nr": "South Ndebele",
"oc": "Occitan",
"oj": "Ojibwe, Ojibwa",
"cu": "Old Church Slavonic, Church Slavic, Church Slavonic, Old Bulgarian, Old Slavonic",
"om": "Oromo",
"or": "Oriya",
"os": "Ossetian, Ossetic",
"pa": "Panjabi, Punjabi",
"pi": "PÄli",
"fa": "Persian (Farsi)",
"pl": "Polish",
"ps": "Pashto, Pushto",
"pt": "Portuguese",
"qu": "Quechua",
"rm": "Romansh",
"rn": "Kirundi",
"ro": "Romanian",
"ru": "Russian",
"sa": "Sanskrit (Saá¹ská¹›ta)",
"sc": "Sardinian",
"sd": "Sindhi",
"se": "Northern Sami",
"sm": "Samoan",
"sg": "Sango",
"sr": "Serbian",
"gd": "Scottish Gaelic; Gaelic",
"sn": "Shona",
"si": "Sinhala, Sinhalese",
"sk": "Slovak",
"sl": "Slovene",
"so": "Somali",
"st": "Southern Sotho",
"az": "South Azerbaijani",
"es": "Spanish; Castilian",
"su": "Sundanese",
"sw": "Swahili",
"ss": "Swati",
"sv": "Swedish",
"ta": "Tamil",
"te": "Telugu",
"tg": "Tajik",
"th": "Thai",
"ti": "Tigrinya",
"bo": "Tibetan Standard, Tibetan, Central",
"tk": "Turkmen",
"tl": "Tagalog",
"tn": "Tswana",
"to": "Tonga (Tonga Islands)",
"tr": "Turkish",
"ts": "Tsonga",
"tt": "Tatar",
"tw": "Twi",
"ty": "Tahitian",
"ug": "Uyghur, Uighur",
"uk": "Ukrainian",
"ur": "Urdu",
"uz": "Uzbek",
"ve": "Venda",
"vi": "Vietnamese",
"vo": "Volapük",
"wa": "Walloon",
"cy": "Welsh",
"wo": "Wolof",
"fy": "Western Frisian",
"xh": "Xhosa",
"yi": "Yiddish",
"yo": "Yoruba",
"za": "Zhuang, Chuang",
"zu": "Zulu"
};
var c = {
"AF": "Afghanistan",
"AX": "Åland",
"AL": "Albanië",
"DZ": "Algerije",
"VI": "Amerikaanse Maagdeneilanden",
"AS": "Amerikaans-Samoa",
"AD": "Andorra",
"AO": "Angola",
"AI": "Anguilla",
"AQ": "Antarctica",
"AG": "Antigua en Barbuda",
"AR": "Argentinië",
"AM": "Armenië",
"AW": "Aruba",
"AU": "Australië",
"AZ": "Azerbeidzjan",
"BS": "Bahama's",
"BH": "Bahrein",
"BD": "Bangladesh",
"BB": "Barbados",
"BE": "België",
"BZ": "Belize",
"BJ": "Benin",
"BM": "Bermuda",
"BT": "Bhutan",
"BO": "Bolivia",
"BQ": "Bonaire, Sint Eustatius en Saba",
"BA": "Bosnië en Herzegovina",
"BW": "Botswana",
"BV": "Bouvet",
"BR": "Brazilië",
"VG": "Britse Maagdeneilanden",
"IO": "Brits Indische Oceaanterritorium",
"BN": "Brunei",
"BG": "Bulgarije",
"BF": "Burkina Faso",
"BI": "Burundi",
"KH": "Cambodja",
"CA": "Canada",
"CF": "Centraal-Afrikaanse Republiek",
"CL": "Chili",
"CN": "China",
"CX": "Christmaseiland",
"CC": "Cocoseilanden",
"CO": "Colombia",
"KM": "Comoren",
"CG": "Congo-Brazzaville",
"CD": "Congo-Kinshasa",
"CK": "Cookeilanden",
"CR": "Costa Rica",
"CU": "Cuba",
"CW": "Curaçao",
"CY": "Cyprus",
"DK": "Denemarken",
"DJ": "Djibouti",
"DM": "Dominica",
"DO": "Dominicaanse Republiek",
"DE": "Duitsland",
"EC": "Ecuador",
"EG": "Egypte",
"SV": "El Salvador",
"GQ": "Equatoriaal-Guinea",
"ER": "Eritrea",
"EE": "Estland",
"ET": "Ethiopië",
"FO": "Faeröer",
"FK": "Falklandeilanden",
"FJ": "Fiji",
"PH": "Filipijnen",
"FI": "Finland",
"FR": "Frankrijk",
"TF": "Franse Zuidelijke en Antarctische Gebiede",
"GF": "Frans-Guyana",
"PF": "Frans-Polynesië",
"GA": "Gabon",
"GM": "Gambia",
"GE": "Georgië",
"GH": "Ghana",
"GI": "Gibraltar",
"GD": "Grenada",
"GR": "Griekenland",
"GL": "Groenland",
"GP": "Guadeloupe",
"GU": "Guam",
"GT": "Guatemala",
"GG": "Guernsey",
"GN": "Guinee",
"GW": "Guinee-Bissau",
"GY": "Guyana",
"HT": "Haïti",
"HM": "Heard en McDonaldeilanden",
"HN": "Honduras",
"HU": "Hongarije",
"HK": "Hongkong",
"IE": "Ierland",
"IS": "IJsland",
"IN": "India",
"ID": "Indonesië",
"IQ": "Irak",
"IR": "Iran",
"IL": "Israël",
"IT": "Italië",
"CI": "Ivoorkust",
"JM": "Jamaica",
"JP": "Japan",
"YE": "Jemen",
"JE": "Jersey",
"JO": "Jordanië",
"KY": "Kaaimaneilanden",
"CV": "Kaapverdië",
"CM": "Kameroen",
"KZ": "Kazachstan",
"KE": "Kenia",
"KG": "Kirgizië",
"KI": "Kiribati",
"UM": "Kleine afgelegen eilanden van de Verenigde Staten",
"KW": "Koeweit",
"XK": "Kosovo",
"HR": "Kroatië",
"LA": "Laos",
"LS": "Lesotho",
"LV": "Letland",
"LB": "Libanon",
"LR": "Liberia",
"LY": "Libië",
"LI": "Liechtenstein",
"LT": "Litouwen",
"LU": "Luxemburg",
"MO": "Macau",
"MK": "Macedonië",
"MG": "Madagaskar",
"MW": "Malawi",
"MV": "Maldiven",
"MY": "Maleisië",
"ML": "Mali",
"MT": "Malta",
"IM": "Man",
"MA": "Marokko",
"MH": "Marshalleilanden",
"MQ": "Martinique",
"MR": "Mauritanië",
"MU": "Mauritius",
"YT": "Mayotte",
"MX": "Mexico",
"FM": "Micronesia",
"MD": "Moldavië",
"MC": "Monaco",
"MN": "Mongolië",
"ME": "Montenegro",
"MS": "Montserrat",
"MZ": "Mozambique",
"MM": "Myanmar",
"NA": "Namibië",
"NR": "Nauru",
"NL": "Nederland",
"NP": "Nepal",
"NI": "Nicaragua",
"NC": "Nieuw-Caledonië",
"NZ": "Nieuw-Zeeland",
"NE": "Niger",
"NG": "Nigeria",
"NU": "Niue",
"MP": "Noordelijke Marianen",
"KP": "Noord-Korea",
"NO": "Noorwegen",
"NF": "Norfolk",
"UG": "Oeganda",
"UA": "Oekraïne",
"UZ": "Oezbekistan",
"OM": "Oman",
"AT": "Oostenrijk",
"TL": "Oost-Timor",
"PK": "Pakistan",
"PW": "Palau",
"PS": "Palestina",
"PA": "Panama",
"PG": "Papua-Nieuw-Guinea",
"PY": "Paraguay",
"PE": "Peru",
"PN": "Pitcairneilanden",
"PL": "Polen",
"PT": "Portugal",
"PR": "Puerto Rico",
"QA": "Qatar",
"RE": "Réunion",
"RO": "Roemenië",
"RU": "Rusland",
"RW": "Rwanda",
"BL": "Saint-Barthélemy",
"KN": "Saint Kitts en Nevis",
"LC": "Saint Lucia",
"PM": "Saint-Pierre en Miquelon",
"VC": "Saint Vincent en de Grenadines",
"SB": "Salomonseilanden",
"WS": "Samoa",
"SM": "San Marino",
"SA": "Saoedi-Arabië",
"ST": "Sao Tomé en Principe",
"SN": "Senegal",
"RS": "Servië",
"SC": "Seychellen",
"SL": "Sierra Leone",
"SG": "Singapore",
"SH": "Sint-Helena, Ascension en Tristan da Cunha",
"MF": "Sint-Maarten",
"SX": "Sint Maarten",
"SI": "Slovenië",
"SK": "Slowakije",
"SD": "Soedan",
"SO": "Somalië",
"ES": "Spanje",
"SJ": "Spitsbergen en Jan Mayen",
"LK": "Sri Lanka",
"SR": "Suriname",
"SZ": "Swaziland",
"SY": "Syrië",
"TJ": "Tadzjikistan",
"TW": "Taiwan",
"TZ": "Tanzania",
"TH": "Thailand",
"TG": "Togo",
"TK": "Tokelau",
"TO": "Tonga",
"TT": "Trinidad en Tobago",
"TD": "Tsjaad",
"CZ": "Tsjechië",
"TN": "Tunesië",
"TR": "Turkije",
"TM": "Turkmenistan",
"TC": "Turks- en Caicoseilanden",
"TV": "Tuvalu",
"UY": "Uruguay",
"VU": "Vanuatu",
"VA": "Vaticaanstad",
"VE": "Venezuela",
"AE": "Verenigde Arabische Emiraten",
"US": "Verenigde Staten",
"GB": "Verenigd Koninkrijk",
"VN": "Vietnam",
"WF": "Wallis en Futuna",
"EH": "Westelijke Sahara",
"BY": "Wit-Rusland",
"ZM": "Zambia",
"ZW": "Zimbabwe",
"ZA": "Zuid-Afrika",
"GS": "Zuid-Georgia en de Zuidelijke Sandwicheilanden",
"KR": "Zuid-Korea",
"SS": "Zuid-Soedan",
"SE": "Zweden",
"CH": "Zwitserland",
};
$scope.fnLC = function (a) {
var b = a.split("_");
return l[b[0]];
}
$scope.fnC = function (a) {
return c[a];
}
}
]);
app.controller('analytics', ['$scope', '$timeout', '$http', '$rootScope', '$controller', '$filter', 'h',
function($scope, $timeout, $http, $rootScope, $controller, $filter, h) {
$scope.cfg = {
authurl: 'https://app.pageowl.app/rest/analytics/connect?websiteid=' + h.siteId(),
dateformat: "YYYYMMDD",
dimensionprefix: 'ga:',
urlbase: 'analytics',
switchclient: true,
report_endpoint: '/rest/analytics/v4',
}
$scope.gloMetrics = h.metrics('analytics');
/* $scope.gloMetrics = {
sessions: { name: 'Sessions', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
users: { name: 'Users', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
pageviews: { name: 'Pageviews', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
pageviewsPerSession: { name: 'Pageviews / session', typefilter: 'number', filtercomparator: 2 ,'datemerge':'avg'},
avgSessionDuration: { name: 'Average session duration', typefilter: 'secondsToHHmmss', filtercomparator: 0 ,'datemerge':'avg'},
percentNewSessions: { name: 'Percentrage new sessions', typefilter: 'number', filtercomparator: 0, 'after': '%' ,'datemerge':'avg'},
bounceRate: { name: 'Bounce Ratio', typefilter: 'number', filtercomparator: 0, 'after': '%' ,'datemerge':'avg'},
goalCompletionsAll: { name: 'Goals', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
goalValueAll: { name: 'Goalvalue', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
goalConversionRateAll: { name: 'Goalconversion ratio', typefilter: 'number', filtercomparator: 0, 'after': '%' ,'datemerge':'avg'},
goalAbandonRateAll: { name: 'Doel verlatings ratio', typefilter: 'number', filtercomparator: 0, 'after': '%' ,'datemerge':'avg'},
uniquePageviews: { name: 'Unique Pageviews', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
avgTimeOnPage: { name: 'Avg time on page', typefilter: 'secondsToHHmmss', filtercomparator: 0 ,'datemerge':'avg'},
entrances: { name: 'Entrances', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
exitRate: { name: 'Exit rate', typefilter: 'number', filtercomparator: 0, 'after': '%' ,'datemerge':'avg'},
pageValue: { name: 'Pagevalue', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
exits: { name: 'Exits', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
ROAS: { name: 'ROAS', typefilter: 'number', filtercomparator: 0 ,'datemerge':'avg'},
costPerTransaction: { name: 'Cost per transaction', typefilter: 'number', filtercomparator: 0 ,'datemerge':'avg'},
totalValue: { name: 'Total value', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
adCost: { name: 'Ad Cost', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
CPC: { name: 'CPC', typefilter: 'number', filtercomparator: 0 ,'datemerge':'avg'},
adClicks: { name: 'Ad Clicks', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
transactions: { name: 'Transacions', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
uniquePurchases: { name: 'Unique Purchases', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
totalValue: { name: 'Total Value', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
revenuePerTransaction: { name: 'Revenue per Transaction', typefilter: 'number', filtercomparator: 0 ,'datemerge':'avg'},
itemQuantity: { name: 'Item Quentity', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
itemsPerPurchase: { name: 'items / purchase', typefilter: 'number', filtercomparator: 0 ,'datemerge':'avg'},
};*/
$scope.selectedmetrics = {};
$scope.reportconfig = {
channels: {
views: {
all: {
name: 'All',
view: 'all',
viewfilters: ['goals'],
first: { dimension: 'date' },
second: { dimension: 'channelGrouping', sort: 'sessions', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'users': true, 'pageviews': true, 'pageviewsPerSession': true, 'avgSessionDuration': true, 'percentNewSessions': true, 'bounceRate': true, 'goalCompletionsAll': true, 'goalValueAll': true, 'goalConversionRateAll': true },
metrics: ['sessions', 'users', 'pageviews', 'pageviewsPerSession', 'avgSessionDuration', 'percentNewSessions', 'bounceRate', 'goalCompletionsAll', 'goalValueAll', 'goalConversionRateAll'],
table: { dataset: 'second' },
panels: { dataset: 'second' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions / channel', type: 'pie', dataset: 'second' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Users / channel', type: 'pie', dataset: 'second' }],
pageviews: [{ 'chart': 'c1', 'name': 'Pagina weergaven', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Pageviews / channel', type: 'pie', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Pageviews per session /channel', type: 'bar', dataset: 'second' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
organic: {
name: 'Organic',
view: 'organic',
viewfilters: ['goals'],
first: { dimension: 'date', channel: 'Organic Search' },
second: { dimension: 'keyword', channel: 'Organic Search', sort: 'users', sortorder: 'DESCENDING' },
third: { dimension: 'source', channel: 'Organic Search', sort: 'users', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'users': true, 'pageviews': true, 'pageviewsPerSession': true, 'avgSessionDuration': true, 'percentNewSessions': true, 'bounceRate': true, 'goalCompletionsAll': true, 'goalValueAll': true, 'goalConversionRateAll': true },
metrics: ['sessions', 'users', 'pageviews', 'pageviewsPerSession', 'avgSessionDuration', 'percentNewSessions', 'bounceRate', 'goalCompletionsAll', 'goalValueAll', 'goalConversionRateAll'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'third' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'third' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'third' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'third' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'third' }],
},
},
paid: {
name: 'Betaalde zoekopdrachten',
view: 'paid',
viewfilters: ['goals'],
first: { dimension: 'date', channel: 'Paid Search' },
second: { dimension: 'keyword', channel: 'Paid Search', sort: 'sessions', sortorder: 'DESCENDING' },
third: { dimension: 'year', channel: 'Paid Search', sort: 'sessions', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'users': true, 'pageviews': true, 'pageviewsPerSession': true, 'avgSessionDuration': true, 'percentNewSessions': true, 'bounceRate': true, 'goalCompletionsAll': true, 'goalValueAll': true, 'goalConversionRateAll': true },
metrics: ['sessions', 'users', 'pageviews', 'pageviewsPerSession', 'avgSessionDuration', 'percentNewSessions', 'bounceRate', 'goalCompletionsAll', 'goalValueAll', 'goalConversionRateAll'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions', type: 'pie', dataset: 'second' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
display: {
name: 'Display',
view: 'display',
viewfilters: ['goals'],
first: { dimension: 'date', channel: 'Display' },
second: { dimension: 'source', channel: 'Display', sort: 'sessions', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'users': true, 'pageviews': true, 'pageviewsPerSession': true, 'avgSessionDuration': true, 'percentNewSessions': true, 'bounceRate': true, 'goalCompletionsAll': true, 'goalValueAll': true, 'goalConversionRateAll': true },
metrics: ['sessions', 'users', 'pageviews', 'pageviewsPerSession', 'avgSessionDuration', 'percentNewSessions', 'bounceRate', 'goalCompletionsAll', 'goalValueAll', 'goalConversionRateAll'],
table: { dataset: 'second' },
panels: { dataset: 'second' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions', type: 'pie', dataset: 'second' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
social: {
name: 'Social',
view: 'social',
viewfilters: ['goals'],
first: { dimension: 'date', channel: 'Social' },
second: { dimension: 'socialNetwork', channel: 'Social', sort: 'sessions', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'users': true, 'pageviews': true, 'pageviewsPerSession': true, 'avgSessionDuration': true, 'percentNewSessions': true, 'bounceRate': true, 'goalCompletionsAll': true, 'goalValueAll': true, 'goalConversionRateAll': true },
metrics: ['sessions', 'users', 'pageviews', 'pageviewsPerSession', 'avgSessionDuration', 'percentNewSessions', 'bounceRate', 'goalCompletionsAll', 'goalValueAll', 'goalConversionRateAll'],
table: { dataset: 'second' },
panels: { dataset: 'second' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions', type: 'pie', dataset: 'second' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
referral: {
name: 'Referral',
view: 'referral',
viewfilters: ['goals'],
first: { dimension: 'date', channel: 'Referral', sort: 'date', sortorder: 'ASCENDING', },
second: { dimension: 'source', channel: 'Referral', sort: 'sessions', sortorder: 'DESCENDING' },
third: { dimension: 'year', channel: 'Referral', sort: 'year', sortorder: 'ASCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'users': true, 'pageviews': true, 'pageviewsPerSession': true, 'avgSessionDuration': true, 'percentNewSessions': true, 'bounceRate': true, 'goalCompletionsAll': true, 'goalValueAll': true, 'goalConversionRateAll': true },
metrics: ['sessions', 'users', 'pageviews', 'pageviewsPerSession', 'avgSessionDuration', 'percentNewSessions', 'bounceRate', 'goalCompletionsAll', 'goalValueAll', 'goalConversionRateAll'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
email: {
name: 'E-mail',
view: 'email',
viewfilters: ['goals'],
first: { dimension: 'date', channel: 'Email' },
second: { dimension: 'source', channel: 'Email', sort: 'sessions', sortorder: 'DESCENDING', },
third: { dimension: 'year', channel: 'Email', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'users': true, 'pageviews': true, 'pageviewsPerSession': true, 'avgSessionDuration': true, 'percentNewSessions': true, 'bounceRate': true, 'goalCompletionsAll': true, 'goalValueAll': true, 'goalConversionRateAll': true },
metrics: ['sessions', 'users', 'pageviews', 'pageviewsPerSession', 'avgSessionDuration', 'percentNewSessions', 'bounceRate', 'goalCompletionsAll', 'goalValueAll', 'goalConversionRateAll'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions', type: 'pie', dataset: 'second' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
other: {
name: 'Overig',
view: 'other',
viewfilters: ['goals'],
first: { dimension: 'date', channel: '(Other)' },
second: { dimension: 'source', channel: '(Other)', sort: 'sessions', sortorder: 'DESCENDING', },
third: { dimension: 'year', channel: '(Other)', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'users': true, 'pageviews': true, 'pageviewsPerSession': true, 'avgSessionDuration': true, 'percentNewSessions': true, 'bounceRate': true, 'goalCompletionsAll': true, 'goalValueAll': true, 'goalConversionRateAll': true },
metrics: ['sessions', 'users', 'pageviews', 'pageviewsPerSession', 'avgSessionDuration', 'percentNewSessions', 'bounceRate', 'goalCompletionsAll', 'goalValueAll', 'goalConversionRateAll'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions', type: 'pie', dataset: 'second' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
},
},
audience: {
views: {
local: {
name: 'Lokaal',
view: 'local',
viewfilters: ['goals'],
first: { dimension: 'country', sort: 'sessions', sortorder: 'DESCENDING' },
second: { dimension: 'country', sort: 'sessions', sortorder: 'DESCENDING' },
third: { dimension: 'year', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'users': true, 'pageviews': true, 'pageviewsPerSession': true, 'avgSessionDuration': true, 'percentNewSessions': true, 'bounceRate': true, 'goalCompletionsAll': true, 'goalValueAll': true, 'goalConversionRateAll': true },
metrics: ['sessions', 'users', 'pageviews', 'pageviewsPerSession', 'avgSessionDuration', 'percentNewSessions', 'bounceRate', 'goalCompletionsAll', 'goalValueAll', 'goalConversionRateAll'],
table: { dataset: 'first' },
panels: { dataset: 'second' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
},
},
age: {
name: 'Leeftijd',
view: 'age',
viewfilters: ['goals'],
first: { dimension: 'date', dimension2: 'userAgeBracket' },
second: { dimension: 'userAgeBracket', sort: 'userAgeBracket', sortorder: 'ASCENDING', },
third: { dimension: 'year', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'users': true, 'pageviews': true, 'pageviewsPerSession': true, 'avgSessionDuration': true, 'percentNewSessions': true, 'bounceRate': true, 'goalCompletionsAll': true, 'goalValueAll': true, 'goalConversionRateAll': true },
metrics: ['sessions', 'users', 'pageviews', 'pageviewsPerSession', 'avgSessionDuration', 'percentNewSessions', 'bounceRate', 'goalCompletionsAll', 'goalValueAll', 'goalConversionRateAll'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'linemulti', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'linemulti', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'linemulti', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'linemulti', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'linemulti', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'linemulti', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'linemulti', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'linemulti', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'linemulti', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'linemulti', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
gender: {
name: 'Geslacht',
view: 'gender',
viewfilters: ['goals'],
first: { dimension: 'date', dimension2: 'userGender', sort: 'userGender', sortorder: 'ASCENDING', },
second: { dimension: 'userGender', sort: 'userGender', sortorder: 'ASCENDING', },
third: { dimension: 'year', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'users': true, 'pageviews': true, 'pageviewsPerSession': true, 'avgSessionDuration': true, 'percentNewSessions': true, 'bounceRate': true, 'goalCompletionsAll': true, 'goalValueAll': true, 'goalConversionRateAll': true },
metrics: ['sessions', 'users', 'pageviews', 'pageviewsPerSession', 'avgSessionDuration', 'percentNewSessions', 'bounceRate', 'goalCompletionsAll', 'goalValueAll', 'goalConversionRateAll'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'linemulti', dataset: 'first', graphs: ['male', 'female'] }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'linemulti', dataset: 'first', graphs: ['male', 'female'] }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'linemulti', dataset: 'first', graphs: ['male', 'female'] }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'linemulti', dataset: 'first', graphs: ['male', 'female'] }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'linemulti', dataset: 'first', graphs: ['male', 'female'] }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'linemulti', dataset: 'first', graphs: ['male', 'female'] }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'linemulti', dataset: 'first', graphs: ['male', 'female'] }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'linemulti', dataset: 'first', graphs: ['male', 'female'] }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'linemulti', dataset: 'first', graphs: ['male', 'female'] }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'linemulti', dataset: 'first', graphs: ['male', 'female'] }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
device: {
name: 'Apparaten',
view: 'device',
viewfilters: ['goals'],
first: { dimension: 'date', dimension2: 'deviceCategory' },
second: { dimension: 'deviceCategory', sort: 'deviceCategory', sortorder: 'ASCENDING', },
third: { dimension: 'year', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'users': true, 'pageviews': true, 'pageviewsPerSession': true, 'avgSessionDuration': true, 'percentNewSessions': true, 'bounceRate': true, 'goalCompletionsAll': true, 'goalValueAll': true, 'goalConversionRateAll': true },
metrics: ['sessions', 'users', 'pageviews', 'pageviewsPerSession', 'avgSessionDuration', 'percentNewSessions', 'bounceRate', 'goalCompletionsAll', 'goalValueAll', 'goalConversionRateAll'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'linemulti', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'] }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'linemulti', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'] }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'linemulti', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'] }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'linemulti', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'] }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'linemulti', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'] }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'linemulti', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'] }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'linemulti', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'] }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'linemulti', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'] }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'linemulti', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'] }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'linemulti', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'] }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
mobile: {
name: 'Mobiel',
view: 'mobile',
viewfilters: ['goals'],
first: { dimension: 'mobileDeviceInfo', sort: 'sessions', sortorder: 'DESCENDING' },
second: { dimension: 'year', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'users': true, 'pageviews': true, 'pageviewsPerSession': true, 'avgSessionDuration': true, 'percentNewSessions': true, 'bounceRate': true, 'goalCompletionsAll': true, 'goalValueAll': true, 'goalConversionRateAll': true },
metrics: ['sessions', 'users', 'pageviews', 'pageviewsPerSession', 'avgSessionDuration', 'percentNewSessions', 'bounceRate', 'goalCompletionsAll', 'goalValueAll', 'goalConversionRateAll'],
table: { dataset: 'first' },
panels: { dataset: 'second' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'progress', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'] }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'progress', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'] }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'progress', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'] }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'progress', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'] }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'progress', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'] }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'progress', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'] }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'progress', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'] }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'progress', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'] }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'progress', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'] }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'progress', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'] }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
},
}
},
},
conversions: {
views: {
goals: {
name: 'Doelen',
view: 'goals',
first: { dimension: 'date', },
second: { dimension: 'goalCompletionsAll' },
third: { dimension: 'year', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'goalCompletionsAll',
selectedmetrics: { 'goalCompletionsAll': true, 'goalValueAll': true, 'goalConversionRateAll': true, 'goalAbandonRateAll': true },
metrics: ['goalCompletionsAll', 'goalValueAll', 'goalConversionRateAll', 'goalAbandonRateAll'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalAbandonRateAll: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
/* DIT KLOPT NIET, HEB NOG GEEN DOELEN OF ECOMMERSE DATA*/
ecommerce: {
name: 'E-commerce',
view: 'ecommerce',
first: { dimension: 'date' },
second: { dimension: 'adMatchedQuery', },
third: { dimension: 'channelGrouping' },
selectedmetric: 'transactions',
selectedmetrics: { 'transactions': true, 'uniquePurchases': true, 'totalValue': true, 'revenuePerTransaction': true, 'itemQuantity': true, 'itemsPerPurchase': true },
metrics: ['transactions', 'uniquePurchases', 'totalValue', 'revenuePerTransaction', 'itemQuantity', 'itemsPerPurchase'],
table: { dataset: 'second' },
panels: { dataset: 'first' },
charts: {
transactions: [{ 'chart': 'c1', 'name': 'transactions', type: 'line', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'third' }],
uniquePurchases: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
totalValue: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
revenuePerTransaction: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
itemQuantity: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
itemsPerPurchase: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
},
},
pages: {
views: {
all: {
name: 'Alle pagina\'s',
view: 'all',
viewfilters: ['goals', 'channels'],
first: { dimension: 'date', },
second: { dimension: 'pagePath', sort: 'pageviews', sortorder: 'DESCENDING', },
third: { dimension: 'year', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'pageviews',
selectedmetrics: { 'pageviews': true, 'uniquePageviews': true, 'pageviewsPerSession': true, 'avgTimeOnPage': true, 'entrances': true, 'bounceRate': true, 'exitRate': true, 'pageValue': true },
metrics: ['pageviews', 'uniquePageviews', 'pageviewsPerSession', 'avgTimeOnPage', 'entrances', 'bounceRate', 'exitRate', 'pageValue'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'progress', dataset: 'second' }],
uniquePageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'progress', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'progress', dataset: 'second' }],
avgTimeOnPage: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'progress', dataset: 'second' }],
entrances: [{ 'chart': 'c1', 'name': 'Ingangen', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'progress', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'progress', dataset: 'second' }],
exitRate: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'progress', dataset: 'second' }],
pageValue: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'progress', dataset: 'second' }],
},
},
/* NIET HELAAM LEKKER */
landing: {
name: 'Landingspagina\'s',
view: 'landing',
first: { dimension: 'date' },
second: { dimension: 'landingPagePath', sort: 'pageviews', sortorder: 'DESCENDING', },
third: { dimension: 'year', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'pageviews',
selectedmetrics: { 'pageviews': true, 'uniquePageviews': true, 'pageviewsPerSession': true, 'avgTimeOnPage': true, 'entrances': true, 'bounceRate': true, 'exitRate': true, 'pageValue': true },
metrics: ['pageviews', 'uniquePageviews', 'pageviewsPerSession', 'avgTimeOnPage', 'entrances', 'bounceRate', 'exitRate', 'pageValue'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'progress', dataset: 'second' }],
uniquePageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'progress', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'progress', dataset: 'second' }],
avgTimeOnPage: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'progress', dataset: 'second' }],
entrances: [{ 'chart': 'c1', 'name': 'Ingangen', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'progress', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'progress', dataset: 'second' }],
exitRate: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'progress', dataset: 'second' }],
pageValue: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'progress', dataset: 'second' }],
},
},
exit: {
name: 'Exit Pagina\'s',
view: 'exit',
first: { dimension: 'date' },
second: { dimension: 'exitPagePath', sort: 'exits', sortorder: 'DESCENDING', },
third: { dimension: 'year', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'exits',
selectedmetrics: { 'exits': true, 'uniquePageviews': true, 'avgTimeOnPage': true, 'exitRate': true },
metrics: ['exits', 'uniquePageviews', 'avgTimeOnPage', 'exitRate'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
exits: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'progress', dataset: 'second' }],
uniquePageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'progress', dataset: 'second' }],
avgTimeOnPage: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'progress', dataset: 'second' }],
exitRate: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'progress', dataset: 'second' }],
},
},
},
},
events: {
views: {
all: {
name: 'Alle pagina\'s',
view: 'all',
first: { dimension: 'date', },
second: { dimension: 'pagePath', sort: 'pageviews', sortorder: 'DESCENDING', },
selectedmetric: 'pageviews',
selectedmetrics: { 'pageviews': true, 'uniquePageviews': true, 'pageviewsPerSession': true, 'avgTimeOnPage': true, 'entrances': true, 'bounceRate': true, 'exitRate': true, 'pageValue': true },
metrics: ['pageviews', 'uniquePageviews', 'pageviewsPerSession', 'avgTimeOnPage', 'entrances', 'bounceRate', 'exitRate', 'pageValue'],
table: { dataset: 'second' },
panels: { dataset: 'first' },
charts: {
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
uniquePageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
avgTimeOnPage: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
entrances: [{ 'chart': 'c1', 'name': 'Ingangen', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
exitRate: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
pageValue: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
/* NIET HELAAM LEKKER */
landing: {
name: 'Landingspagina\'s',
view: 'landing',
first: { dimension: 'date' },
second: { dimension: 'landingPagePath', sort: 'pageviews', sortorder: 'DESCENDING', },
selectedmetric: 'pageviews',
selectedmetrics: { 'pageviews': true, 'uniquePageviews': true, 'pageviewsPerSession': true, 'avgTimeOnPage': true, 'entrances': true, 'bounceRate': true, 'exitRate': true, 'pageValue': true },
metrics: ['pageviews', 'uniquePageviews', 'pageviewsPerSession', 'avgTimeOnPage', 'entrances', 'bounceRate', 'exitRate', 'pageValue'],
table: { dataset: 'second' },
panels: { dataset: 'first' },
charts: {
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
uniquePageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
avgTimeOnPage: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
entrances: [{ 'chart': 'c1', 'name': 'Ingangen', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
exitRate: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
pageValue: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
exit: {
name: 'Exit Pagina\'s',
view: 'exit',
first: { dimension: 'date' },
second: { dimension: 'exitPagePath', sort: 'exits', sortorder: 'DESCENDING', },
selectedmetric: 'exits',
selectedmetrics: { 'exits': true, 'uniquePageviews': true, 'avgTimeOnPage': true, 'exitRate': true },
metrics: ['exits', 'uniquePageviews', 'avgTimeOnPage', 'exitRate'],
table: { dataset: 'second' },
panels: { dataset: 'first' },
charts: {
exits: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
uniquePageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
avgTimeOnPage: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
exitRate: [{ 'chart': 'c1', 'name': '_', type: 'line', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
},
}
}
$scope.setAnalyticsCustomer = function(data) {
$http.get('/rest/' + $scope.cfg.urlbase + '/profileid.set', {
params: {
websiteid: h.siteId(),
profileid: data.profileid,
accountId: data.accountId,
webPropertyId: data.webPropertyId,
}
}).then(function successCallback(res) {
$scope.getReportData();
});
}
$controller('coreapi', { $scope: $scope });
$scope.init();
}
]);
app.controller('analytics4', ['$scope', '$timeout', '$http', '$rootScope', '$controller', '$filter', 'h',
function ($scope, $timeout, $http, $rootScope, $controller, $filter, h) {
$scope.cfg = {
authurl: 'https://app.marketingtracer.com/rest/analytics4/connect?websiteid=' + h.siteId(),
dateformat: "YYYYMMDD",
dimensionprefix: '',
urlbase: 'analytics4',
switchclient: true,
report_endpoint: '/rest/analytics4/report',
}
$scope.checkDataAvailability = function (data) {
if(data.data && data.data.length == 0){
$scope.msg = 'Google Signals Must Be Active: Age and gender data are only collected if you have Google Signals enabled for the GA4 property. If it\'s not active, the API will return no data. You must enable this in your GA4 Admin settings (Data Settings > Data Collection).';
}
}
if($rootScope.user.reseller?.config?.authdomain){
$scope.cfg.authurl = 'https://' + $rootScope.user.reseller.config.authdomain + '/rest/analytics4/connect?websiteid=' + h.siteId();
}
//$scope.gloMetrics = h.metrics('analytics');
$scope.gloMetrics = {
sessions: { name: 'Sessions', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
totalUsers: { name: 'totalUsers', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
screenPageViews: { name: 'screenPageViews', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
screenPageViewsPerSession: { name: 'screenPageViewsPerSession', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
sessions: { name: 'Sessions', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
newUsers: { name: 'Users', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
pageviews: { name: 'Pageviews', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
pageviewsPerSession: { name: 'Pageviews / session', typefilter: 'number', filtercomparator: 2, 'datemerge': 'avg' },
averageSessionDuration: { name: 'Average session duration', typefilter: 'secondsToHHmmss', filtercomparator: 0, 'datemerge': 'avg' },
percentNewSessions: { name: 'Percentrage new sessions', typefilter: 'number', filtercomparator: 0, 'after': '%', 'datemerge': 'avg' },
bounceRate: { name: 'Bounce Ratio', typefilter: 'number', filtercomparator: 0, 'after': '%', 'datemerge': 'avg' },
goalCompletionsAll: { name: 'Goals', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
goalValueAll: { name: 'Goalvalue', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
goalConversionRateAll: { name: 'Goalconversion ratio', typefilter: 'number', filtercomparator: 0, 'after': '%', 'datemerge': 'avg' },
goalAbandonRateAll: { name: 'Doel verlatings ratio', typefilter: 'number', filtercomparator: 0, 'after': '%', 'datemerge': 'avg' },
uniquePageviews: { name: 'Unique Pageviews', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
avgTimeOnPage: { name: 'Avg time on page', typefilter: 'secondsToHHmmss', filtercomparator: 0, 'datemerge': 'avg' },
entrances: { name: 'Entrances', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
exitRate: { name: 'Exit rate', typefilter: 'number', filtercomparator: 0, 'after': '%', 'datemerge': 'avg' },
pageValue: { name: 'Pagevalue', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
exits: { name: 'Exits', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
ROAS: { name: 'ROAS', typefilter: 'number', filtercomparator: 0, 'datemerge': 'avg' },
costPerTransaction: { name: 'Cost per transaction', typefilter: 'number', filtercomparator: 0, 'datemerge': 'avg' },
totalValue: { name: 'Total value', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
adCost: { name: 'Ad Cost', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
CPC: { name: 'CPC', typefilter: 'number', filtercomparator: 0, 'datemerge': 'avg' },
adClicks: { name: 'Ad Clicks', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
transactions: { name: 'Transacions', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
uniquePurchases: { name: 'Unique Purchases', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
totalValue: { name: 'Total Value', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
revenuePerTransaction: { name: 'Revenue per Transaction', typefilter: 'number', filtercomparator: 0, 'datemerge': 'avg' },
itemQuantity: { name: 'Item Quentity', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
itemsPerPurchase: { name: 'items / purchase', typefilter: 'number', filtercomparator: 0, 'datemerge': 'avg' },
eventCount: { name: 'items / purchase', typefilter: 'number', filtercomparator: 0, 'datemerge': 'avg' },
eventCountPerUser: { name: 'items / purchase', typefilter: 'number', filtercomparator: 0, 'datemerge': 'avg' },
mobileDeviceBranding: { name: 'Mobile device model', typefilter: 'text', filtercomparator: 3 },
itemsPurchased: { name: 'Items purchased', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
itemRevenue: { name: 'Item revenue', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
conversions: { name: 'Conversions', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
eventValue: { name: 'Event value', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
sessionConversionRate: { name: 'Session conversion rate', typefilter: 'number', filtercomparator: 0, 'after': '%', 'datemerge': 'avg' },
userEngagementDuration: { name: 'User engagement duration', typefilter: 'secondsToHHmmss', filtercomparator: 0, 'datemerge': 'sum' },
engagementRate: { name: 'User engagement rate', typefilter: 'number', filtercomparator: 2, 'after': '%', 'datemerge': 'avg' },
totalRevenue: { name: 'Total Revenue', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
};
$scope.selectedmetrics = {};
$scope.reportconfig = {
channels: {
views: {
all: {
name: 'All',
view: 'all',
viewfilters: ['goals'],
first: { dimension: 'date' },
second: { dimension: 'sessionDefaultChannelGrouping', sort: 'sessions', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'totalUsers': true, 'screenPageViews': true, 'screenPageViewsPerSession': true, 'averageSessionDuration': true, 'newUsers': true, 'bounceRate': true, 'bounceRate': true, 'eventCount': true, 'eventCountPerUser': true },
metrics: ['sessions', 'totalUsers', 'screenPageViews', 'screenPageViewsPerSession', 'averageSessionDuration', 'newUsers', 'bounceRate', 'eventCount', 'eventCountPerUser'],
table: { dataset: 'second' },
panels: { dataset: 'second' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions / channel', type: 'pie', dataset: 'second' }],
totalUsers: [{ 'chart': 'c1', 'name': 'Users', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Users / channel', type: 'pie', dataset: 'second' }],
screenPageViews: [{ 'chart': 'c1', 'name': 'Pagina weergaven', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'screenPageViews / channel', type: 'pie', dataset: 'second' }],
screenPageViewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'screenPageViewsPerSession per session /channel', type: 'bar', dataset: 'second' }],
averageSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
newUsers: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
eventCount: [{ 'chart': 'c1', 'name': 'Goals', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
eventCountPerUser: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
},
},
organic: {
name: 'Organic',
view: 'organic',
viewfilters: ['goals'],
first: { dimension: 'date', channel: 'Organic Search' },
second: { dimension: 'landingPage', channel: 'Organic Search', sort: 'users', sortorder: 'DESCENDING' },
third: { dimension: 'sessionSource', channel: 'Organic Search', sort: 'users', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'totalUsers': true, 'screenPageViews': true, 'screenPageViewsPerSession': true, 'averageSessionDuration': true, 'newUsers': true, 'bounceRate': true, 'bounceRate': true, 'eventCount': true, 'eventCountPerUser': true },
metrics: ['sessions', 'totalUsers', 'screenPageViews', 'screenPageViewsPerSession', 'averageSessionDuration', 'newUsers', 'bounceRate', 'eventCount', 'eventCountPerUser'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'third' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'third' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'third' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'third' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'third' }],
},
},
// AI-assistenten (ChatGPT, Perplexity, Gemini, Copilot): GA4-kanaal 'AI Assistant'
ai: {
name: 'AI assistants',
view: 'ai',
viewfilters: ['goals'],
first: { dimension: 'date', channel: 'AI Assistant' },
second: { dimension: 'landingPage', channel: 'AI Assistant', sort: 'sessions', sortorder: 'DESCENDING' },
third: { dimension: 'sessionSource', channel: 'AI Assistant', sort: 'sessions', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'totalUsers': true, 'screenPageViews': true, 'screenPageViewsPerSession': true, 'averageSessionDuration': true, 'newUsers': true, 'bounceRate': true, 'eventCount': true, 'eventCountPerUser': true },
metrics: ['sessions', 'totalUsers', 'screenPageViews', 'screenPageViewsPerSession', 'averageSessionDuration', 'newUsers', 'bounceRate', 'eventCount', 'eventCountPerUser'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
},
},
// Direct: vaak veel botverkeer, daarom per land
direct: {
name: 'Direct',
view: 'direct',
viewfilters: ['goals'],
first: { dimension: 'date', channel: 'Direct' },
second: { dimension: 'landingPage', channel: 'Direct', sort: 'sessions', sortorder: 'DESCENDING' },
third: { dimension: 'country', channel: 'Direct', sort: 'sessions', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'totalUsers': true, 'screenPageViews': true, 'screenPageViewsPerSession': true, 'averageSessionDuration': true, 'newUsers': true, 'bounceRate': true, 'eventCount': true, 'eventCountPerUser': true },
metrics: ['sessions', 'totalUsers', 'screenPageViews', 'screenPageViewsPerSession', 'averageSessionDuration', 'newUsers', 'bounceRate', 'eventCount', 'eventCountPerUser'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
},
},
paid: {
name: 'Betaalde zoekopdrachten',
view: 'paid',
viewfilters: ['goals'],
first: { dimension: 'date', channel: 'Paid Search' },
second: { dimension: 'landingPage', channel: 'Paid Search', sort: 'sessions', sortorder: 'DESCENDING' },
third: { dimension: 'year', channel: 'Paid Search', sort: 'sessions', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'totalUsers': true, 'screenPageViews': true, 'screenPageViewsPerSession': true, 'averageSessionDuration': true, 'newUsers': true, 'bounceRate': true, 'bounceRate': true, 'eventCount': true, 'eventCountPerUser': true },
metrics: ['sessions', 'totalUsers', 'screenPageViews', 'screenPageViewsPerSession', 'averageSessionDuration', 'newUsers', 'bounceRate', 'eventCount', 'eventCountPerUser'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions', type: 'pie', dataset: 'second' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
display: {
name: 'Display',
view: 'display',
viewfilters: ['goals'],
first: { dimension: 'date', channel: 'Display' },
second: { dimension: 'source', channel: 'Display', sort: 'sessions', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'totalUsers': true },
metrics: ['sessions', 'totalUsers'],
table: { dataset: 'second' },
panels: { dataset: 'second' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions', type: 'pie', dataset: 'second' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
social: {
name: 'Social',
view: 'social',
viewfilters: ['goals'],
first: { dimension: 'date', channel: 'Organic Social,Paid Social' },
second: { dimension: 'sessionSource', channel: 'Organic Social,Paid Social', sort: 'sessions', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'totalUsers': true, 'screenPageViews': true, 'screenPageViewsPerSession': true, 'averageSessionDuration': true, 'newUsers': true, 'bounceRate': true, 'bounceRate': true, 'eventCount': true, 'eventCountPerUser': true },
metrics: ['sessions', 'totalUsers', 'screenPageViews', 'screenPageViewsPerSession', 'averageSessionDuration', 'newUsers', 'bounceRate', 'eventCount', 'eventCountPerUser'],
table: { dataset: 'second' },
panels: { dataset: 'second' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions', type: 'pie', dataset: 'second' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
referral: {
name: 'Referral',
view: 'referral',
viewfilters: ['goals'],
first: { dimension: 'date', channel: 'Referral', sort: 'date', sortorder: 'ASCENDING', },
second: { dimension: 'sessionSource', channel: 'Referral', sort: 'sessions', sortorder: 'DESCENDING' },
third: { dimension: 'year', channel: 'Referral', sort: 'year', sortorder: 'ASCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'totalUsers': true, 'screenPageViews': true, 'screenPageViewsPerSession': true, 'averageSessionDuration': true, 'newUsers': true, 'bounceRate': true, 'bounceRate': true, 'eventCount': true, 'eventCountPerUser': true },
metrics: ['sessions', 'totalUsers', 'screenPageViews', 'screenPageViewsPerSession', 'averageSessionDuration', 'newUsers', 'bounceRate', 'eventCount', 'eventCountPerUser'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
email: {
name: 'E-mail',
view: 'email',
viewfilters: ['goals'],
first: { dimension: 'date', channel: 'Email' },
second: { dimension: 'sessionSource', channel: 'Email', sort: 'sessions', sortorder: 'DESCENDING', },
third: { dimension: 'year', channel: 'Email', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'totalUsers': true, 'screenPageViews': true, 'screenPageViewsPerSession': true, 'averageSessionDuration': true, 'newUsers': true, 'bounceRate': true, 'bounceRate': true, 'eventCount': true, 'eventCountPerUser': true },
metrics: ['sessions', 'totalUsers', 'screenPageViews', 'screenPageViewsPerSession', 'averageSessionDuration', 'newUsers', 'bounceRate', 'eventCount', 'eventCountPerUser'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions', type: 'pie', dataset: 'second' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
other: {
name: 'Overig',
view: 'other',
viewfilters: ['goals'],
first: { dimension: 'date', channel: '!Direct,Organic Search,AI Assistant,Paid Search,Display,Organic Social,Paid Social,Referral,Email' },
second: { dimension: 'sessionSource', channel: '!Direct,Organic Search,AI Assistant,Paid Search,Display,Organic Social,Paid Social,Referral,Email', sort: 'sessions', sortorder: 'DESCENDING', },
third: { dimension: 'year', channel: '!Direct,Organic Search,AI Assistant,Paid Search,Display,Organic Social,Paid Social,Referral,Email', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'totalUsers': true, 'screenPageViews': true, 'screenPageViewsPerSession': true, 'averageSessionDuration': true, 'newUsers': true, 'bounceRate': true, 'bounceRate': true, 'eventCount': true, 'eventCountPerUser': true },
metrics: ['sessions', 'totalUsers', 'screenPageViews', 'screenPageViewsPerSession', 'averageSessionDuration', 'newUsers', 'bounceRate', 'eventCount', 'eventCountPerUser'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions', type: 'pie', dataset: 'second' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
},
},
audience: {
views: {
local: {
name: 'Lokaal',
view: 'local',
viewfilters: ['goals'],
first: { dimension: 'country', sort: 'sessions', sortorder: 'DESCENDING' },
second: { dimension: 'country', sort: 'sessions', sortorder: 'DESCENDING' },
third: { dimension: 'country', sort: 'sessions', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'totalUsers': true, 'screenPageViews': true, 'screenPageViewsPerSession': true, 'averageSessionDuration': true, 'newUsers': true, 'bounceRate': true, 'bounceRate': true, 'eventCount': true, 'eventCountPerUser': true },
metrics: ['sessions', 'totalUsers', 'screenPageViews', 'screenPageViewsPerSession', 'averageSessionDuration', 'newUsers', 'bounceRate', 'eventCount', 'eventCountPerUser'],
table: { dataset: 'first' },
panels: { dataset: 'second' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'third' }],
},
},
age: {
name: 'Leeftijd',
view: 'age',
viewfilters: ['goals'],
first: { dimension: 'date', dimension2: 'userAgeBracket',callback:$scope.checkDataAvailability },
second: { dimension: 'userAgeBracket', sort: 'userAgeBracket', sortorder: 'ASCENDING' },
third: { dimension: 'year', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'totalUsers': true, 'screenPageViews': true, 'screenPageViewsPerSession': true, 'averageSessionDuration': true, 'newUsers': true, 'bounceRate': true, 'eventCount': true, 'eventCountPerUser': true },
metrics: ['sessions', 'totalUsers', 'screenPageViews', 'screenPageViewsPerSession', 'averageSessionDuration', 'newUsers', 'bounceRate', 'eventCount', 'eventCountPerUser'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'linemulti', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'linemulti', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'linemulti', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'linemulti', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'linemulti', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'linemulti', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'linemulti', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'linemulti', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'linemulti', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'linemulti', dataset: 'first', graphs: ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
gender: {
name: 'Geslacht',
view: 'gender',
viewfilters: ['goals'],
first: { dimension: 'date', dimension2: 'userGender' ,callback:$scope.checkDataAvailability},
second: { dimension: 'userGender', sort: 'userGender', sortorder: 'ASCENDING' },
third: { dimension: 'year', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'totalUsers': true, 'screenPageViews': true, 'screenPageViewsPerSession': true, 'averageSessionDuration': true, 'newUsers': true, 'bounceRate': true, 'eventCount': true, 'eventCountPerUser': true },
metrics: ['sessions', 'totalUsers', 'screenPageViews', 'screenPageViewsPerSession', 'averageSessionDuration', 'newUsers', 'bounceRate', 'eventCount', 'eventCountPerUser'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'linemulti', dataset: 'first', graphs: ['male', 'female', 'unknown'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'linemulti', dataset: 'first', graphs: ['male', 'female', 'unknown'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'linemulti', dataset: 'first', graphs: ['male', 'female', 'unknown'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'linemulti', dataset: 'first', graphs: ['male', 'female', 'unknown'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'linemulti', dataset: 'first', graphs: ['male', 'female', 'unknown'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'linemulti', dataset: 'first', graphs: ['male', 'female', 'unknown'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'linemulti', dataset: 'first', graphs: ['male', 'female', 'unknown'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'linemulti', dataset: 'first', graphs: ['male', 'female', 'unknown'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'linemulti', dataset: 'first', graphs: ['male', 'female', 'unknown'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'linemulti', dataset: 'first', graphs: ['male', 'female', 'unknown'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
device: {
name: 'Apparaten',
view: 'device',
viewfilters: ['goals'],
first: { dimension: 'date', dimension2: 'deviceCategory' },
second: { dimension: 'deviceCategory', sort: 'deviceCategory', sortorder: 'ASCENDING' },
third: { dimension: 'year', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'totalUsers': true, 'screenPageViews': true, 'screenPageViewsPerSession': true, 'averageSessionDuration': true, 'newUsers': true, 'bounceRate': true, 'eventCount': true, 'eventCountPerUser': true },
metrics: ['sessions', 'totalUsers', 'screenPageViews', 'screenPageViewsPerSession', 'averageSessionDuration', 'newUsers', 'bounceRate', 'eventCount', 'eventCountPerUser'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'linemulti', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'linemulti', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'linemulti', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'linemulti', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'linemulti', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'linemulti', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'linemulti', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'linemulti', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'linemulti', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'], date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'linemulti', dataset: 'first', graphs: ['desktop', 'mobile', 'tablet'], date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
mobile: {
name: 'Mobiel',
view: 'mobile',
viewfilters: ['goals'],
first: { dimension: 'mobileDeviceBranding', sort: 'sessions', sortorder: 'DESCENDING' },
second: { dimension: 'mobileDeviceBranding', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'totalUsers': true, 'screenPageViews': true, 'screenPageViewsPerSession': true, 'averageSessionDuration': true, 'newUsers': true, 'bounceRate': true, 'eventCount': true, 'eventCountPerUser': true },
metrics: ['sessions', 'totalUsers', 'screenPageViews', 'screenPageViewsPerSession', 'averageSessionDuration', 'newUsers', 'bounceRate', 'eventCount', 'eventCountPerUser'],
table: { dataset: 'first' },
panels: { dataset: 'second' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'progress', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
users: [{ 'chart': 'c1', 'name': 'Users', type: 'progress', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
pageviews: [{ 'chart': 'c1', 'name': 'PageViews', type: 'progress', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
pageviewsPerSession: [{ 'chart': 'c1', 'name': 'Pageviews per session', type: 'progress', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
avgSessionDuration: [{ 'chart': 'c1', 'name': 'Average session duration', type: 'progress', dataset: 'first' }, { 'chart': 'c2', 'name': '_', 'type': 'pie', dataset: 'first' }],
percentNewSessions: [{ 'chart': 'c1', 'name': 'Percentage new Sessions', type: 'progress', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
bounceRate: [{ 'chart': 'c1', 'name': 'Bounce ratio', type: 'progress', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
goalCompletionsAll: [{ 'chart': 'c1', 'name': 'Goals', type: 'progress', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
goalValueAll: [{ 'chart': 'c1', 'name': 'Goalvalue', type: 'progress', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
goalConversionRateAll: [{ 'chart': 'c1', 'name': 'Goalconverions ratio', type: 'progress', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
},
}
},
},
conversions: {
views: {
goals: {
name: 'Conversies',
view: 'goals',
first: { dimension: 'date' },
second: { dimension: 'eventName', sort: 'conversions', sortorder: 'DESCENDING' },
third: { dimension: 'year', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'conversions',
selectedmetrics: { 'conversions': true, 'sessionConversionRate': true, 'eventValue': true },
metrics: ['conversions', 'sessionConversionRate', 'eventValue'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
conversions: [{ 'chart': 'c1', 'name': 'Conversies', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Conversies per gebeurtenis', type: 'pie', dataset: 'second' }],
sessionConversionRate: [{ 'chart': 'c1', 'name': 'Sessie conversieratio', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Conversieratio per gebeurtenis', type: 'bar', dataset: 'second' }],
eventValue: [{ 'chart': 'c1', 'name': 'Gebeurteniswaarde', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Waarde per gebeurtenis', type: 'pie', dataset: 'second' }],
},
},
ecommerce: {
name: 'Productprestaties',
view: 'ecommerce',
first: { dimension: 'date' },
second: { dimension: 'itemName', sort: 'itemsPurchased', sortorder: 'DESCENDING' },
third: { dimension: 'sessionDefaultChannelGrouping', sort: 'itemRevenue', sortorder: 'DESCENDING' },
selectedmetric: 'itemsPurchased',
// Metrics are now item-scoped to be compatible with itemName
selectedmetrics: { 'itemsPurchased': true, 'itemRevenue': true },
metrics: ['itemsPurchased', 'itemRevenue'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
itemsPurchased: [{ 'chart': 'c1', 'name': 'Aantal gekochte items', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Items per product', type: 'bar', dataset: 'second' }],
itemRevenue: [{ 'chart': 'c1', 'name': 'Productopbrengst', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Opbrengst per product', type: 'pie', dataset: 'second' }],
},
},
ecommerce_page: {
name: 'Ecommerce per Pagina',
view: 'ecommerce_page',
first: { dimension: 'date' },
second: { dimension: 'landingPage', sort: 'totalRevenue', sortorder: 'DESCENDING' },
third: { dimension: 'year', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'totalRevenue',
selectedmetrics: { 'sessions': true, 'totalRevenue': true },
metrics: ['sessions', 'totalRevenue'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessies', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessies per pagina', type: 'bar', dataset: 'second' }],
totalRevenue: [{ 'chart': 'c1', 'name': 'Totale opbrengst', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Totale opbrengst per pagina', type: 'pie', dataset: 'second' }]
},
},
ecommerce_channel: {
name: 'Ecommerce per Kanaal',
view: 'ecommerce_channel',
first: { dimension: 'date' },
second: { dimension: 'sessionDefaultChannelGrouping', sort: 'totalRevenue', sortorder: 'DESCENDING' },
third: { dimension: 'year', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'totalRevenue',
selectedmetrics: { 'sessions': true, 'totalRevenue': true },
metrics: ['sessions', 'totalRevenue'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessies', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessies per kanaal', type: 'bar', dataset: 'second' }],
totalRevenue: [{ 'chart': 'c1', 'name': 'Totale opbrengst', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Totale opbrengst per kanaal', type: 'pie', dataset: 'second' }]
},
},
},
},
pages: {
views: {
all: {
name: 'Alle Pagina\'s',
view: 'all',
viewfilters: ['goals', 'channels'],
first: { dimension: 'date' },
second: { dimension: 'landingPage', sort: 'screenPageViews', sortorder: 'DESCENDING' },
third: { dimension: 'year', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'screenPageViews',
selectedmetrics: { 'screenPageViews': true, 'totalUsers': true, 'userEngagementDuration': true, 'engagementRate': true, 'conversions': true },
metrics: ['screenPageViews', 'totalUsers', 'userEngagementDuration', 'engagementRate', 'conversions'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
screenPageViews: [{ 'chart': 'c1', 'name': 'Paginaweergaven', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Pagina\'s', type: 'progress', dataset: 'second' }],
totalUsers: [{ 'chart': 'c1', 'name': 'Gebruikers', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Pagina\'s', type: 'progress', dataset: 'second' }],
userEngagementDuration: [{ 'chart': 'c1', 'name': 'Gemiddelde Betrokkenheidsduur', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Pagina\'s', type: 'progress', dataset: 'second' }],
engagementRate: [{ 'chart': 'c1', 'name': 'Betrokkenheidspercentage', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Pagina\'s', type: 'progress', dataset: 'second' }],
conversions: [{ 'chart': 'c1', 'name': 'Conversies', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Pagina\'s', type: 'progress', dataset: 'second' }],
},
},
landing: {
name: 'Landingspagina\'s',
view: 'landing',
first: { dimension: 'date' },
second: { dimension: 'landingPage', sort: 'sessions', sortorder: 'DESCENDING' },
third: { dimension: 'year', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'sessions',
selectedmetrics: { 'sessions': true, 'totalUsers': true, 'averageSessionDuration': true, 'engagementRate': true, 'conversions': true },
metrics: ['sessions', 'totalUsers', 'averageSessionDuration', 'engagementRate', 'conversions'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
sessions: [{ 'chart': 'c1', 'name': 'Sessies', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Landingspagina\'s', type: 'progress', dataset: 'second' }],
totalUsers: [{ 'chart': 'c1', 'name': 'Gebruikers', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Landingspagina\'s', type: 'progress', dataset: 'second' }],
averageSessionDuration: [{ 'chart': 'c1', 'name': 'Gemiddelde Sessieduur', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Landingspagina\'s', type: 'progress', dataset: 'second' }],
engagementRate: [{ 'chart': 'c1', 'name': 'Betrokkenheidspercentage', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Landingspagina\'s', type: 'progress', dataset: 'second' }],
conversions: [{ 'chart': 'c1', 'name': 'Conversies', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Landingspagina\'s', type: 'progress', dataset: 'second' }],
},
},
titles: {
name: 'Paginatitels',
view: 'titles',
first: { dimension: 'date' },
second: { dimension: 'pageTitle', sort: 'screenPageViews', sortorder: 'DESCENDING' },
third: { dimension: 'year', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'screenPageViews',
selectedmetrics: { 'screenPageViews': true, 'totalUsers': true, 'userEngagementDuration': true, 'engagementRate': true, 'conversions': true },
metrics: ['screenPageViews', 'totalUsers', 'userEngagementDuration', 'engagementRate', 'conversions'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
screenPageViews: [{ 'chart': 'c1', 'name': 'Paginaweergaven', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Pagina\'s', type: 'progress', dataset: 'second' }],
totalUsers: [{ 'chart': 'c1', 'name': 'Gebruikers', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Pagina\'s', type: 'progress', dataset: 'second' }],
userEngagementDuration: [{ 'chart': 'c1', 'name': 'Gemiddelde Betrokkenheidsduur', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Pagina\'s', type: 'progress', dataset: 'second' }],
engagementRate: [{ 'chart': 'c1', 'name': 'Betrokkenheidspercentage', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Pagina\'s', type: 'progress', dataset: 'second' }],
conversions: [{ 'chart': 'c1', 'name': 'Conversies', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Pagina\'s', type: 'progress', dataset: 'second' }],
},
},
},
},
events: {
views: {
all: {
name: 'Alle Gebeurtenissen',
view: 'all',
first: { dimension: 'date' },
second: { dimension: 'eventName', sort: 'eventCount', sortorder: 'DESCENDING' },
third: { dimension: 'year', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'eventCount',
selectedmetrics: { 'eventCount': true, 'totalUsers': true, 'eventCountPerUser': true, 'eventValue': true },
metrics: ['eventCount', 'totalUsers', 'eventCountPerUser', 'eventValue'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
eventCount: [{ 'chart': 'c1', 'name': 'Aantal Gebeurtenissen', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Gebeurtenissen', type: 'bar', dataset: 'second' }],
totalUsers: [{ 'chart': 'c1', 'name': 'Totaal Aantal Gebruikers', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Gebeurtenissen', type: 'pie', dataset: 'second' }],
eventCountPerUser: [{ 'chart': 'c1', 'name': 'Gebeurtenissen per Gebruiker', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Gebeurtenissen', type: 'bar', dataset: 'second' }],
eventValue: [{ 'chart': 'c1', 'name': 'Gebeurteniswaarde', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Gebeurtenissen', type: 'pie', dataset: 'second' }],
},
},
conversions: {
name: 'Conversies',
view: 'conversions',
first: { dimension: 'date', filter: { 'isConversionEvent': true } },
second: { dimension: 'eventName', sort: 'eventCount', sortorder: 'DESCENDING', filter: { 'isConversionEvent': true } },
third: { dimension: 'year', sort: 'year', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'eventCount',
selectedmetrics: { 'eventCount': true, 'totalUsers': true, 'eventCountPerUser': true, 'eventValue': true },
metrics: ['eventCount', 'totalUsers', 'eventCountPerUser', 'eventValue'],
table: { dataset: 'second' },
panels: { dataset: 'third' },
charts: {
eventCount: [{ 'chart': 'c1', 'name': 'Aantal Conversies', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Conversies', type: 'bar', dataset: 'second' }],
totalUsers: [{ 'chart': 'c1', 'name': 'Totaal Aantal Gebruikers', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Conversies', type: 'pie', dataset: 'second' }],
eventCountPerUser: [{ 'chart': 'c1', 'name': 'Conversies per Gebruiker', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Conversies', type: 'bar', dataset: 'second' }],
eventValue: [{ 'chart': 'c1', 'name': 'Conversiewaarde', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Conversies', type: 'pie', dataset: 'second' }],
},
},
},
}
}
$scope.setAnalyticsCustomer = function (data) {
$http.get('/rest/' + $scope.cfg.urlbase + '/profileid.set', {
params: {
websiteid: h.siteId(),
property: data.property
}
}).then(function successCallback(res) {
$scope.getReportData();
});
}
$controller('coreapi', { $scope: $scope });
$scope.init();
}
]);
app.controller('matomo', ['$scope', '$timeout', '$http', '$rootScope', '$controller', '$filter', 'h',
function ($scope, $timeout, $http, $rootScope, $controller, $filter, h) {
$scope.cfg = {
dateformat: "YYYY-MM-DD",
dimensionprefix: '',
urlbase: 'matomo',
switchclient: true,
report_endpoint: '/rest/matomo/report',
}
$scope.gloMetrics = {
nb_visits: { name: 'Sessions', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
nb_actions: { name: 'Actions', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
nb_actions_per_visit: { name: 'Actions/Visit', typefilter: 'number', filtercomparator: 2, 'datemerge': 'avg' },
bounce_rate: { name: 'Bounce Rate', typefilter: 'number', filtercomparator: 0, 'after': '%', 'datemerge': 'avg' },
avg_time_on_site: { name: 'Avg Session Duration', typefilter: 'secondsToHHmmss', filtercomparator: 0, 'datemerge': 'avg' },
nb_hits: { name: 'Pageviews', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
avg_time_on_page: { name: 'Avg Time on Page', typefilter: 'secondsToHHmmss', filtercomparator: 0, 'datemerge': 'avg' },
nb_conversions: { name: 'Conversions', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
revenue: { name: 'Revenue', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
nb_searches: { name: 'Site Searches', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
};
$scope.selectedmetrics = {};
$scope.reportconfig = {
visitors: {
views: {
all: {
name: 'All',
view: 'all',
first: { dimension: 'date' },
second: { dimension: 'referrerType', sort: 'nb_visits', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'nb_visits',
selectedmetrics: { 'nb_visits': true, 'nb_actions': true, 'nb_actions_per_visit': true, 'avg_time_on_site': true, 'bounce_rate': true },
metrics: ['nb_visits', 'nb_actions', 'nb_actions_per_visit', 'avg_time_on_site', 'bounce_rate'],
table: { dataset: 'second' },
panels: { dataset: 'first' },
charts: {
nb_visits: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions / referrer', type: 'pie', dataset: 'second' }],
nb_actions: [{ 'chart': 'c1', 'name': 'Actions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Actions / referrer', type: 'pie', dataset: 'second' }],
nb_actions_per_visit: [{ 'chart': 'c1', 'name': 'Actions per Visit', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Actions/Visit / referrer', type: 'bar', dataset: 'second' }],
avg_time_on_site: [{ 'chart': 'c1', 'name': 'Avg Session Duration', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounce_rate: [{ 'chart': 'c1', 'name': 'Bounce Rate', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
geographic: {
name: 'Geographic',
view: 'geographic',
first: { dimension: 'country', sort: 'nb_visits', sortorder: 'DESCENDING' },
second: { dimension: 'country', sort: 'nb_visits', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'nb_visits',
selectedmetrics: { 'nb_visits': true, 'nb_actions': true, 'nb_actions_per_visit': true, 'avg_time_on_site': true, 'bounce_rate': true },
metrics: ['nb_visits', 'nb_actions', 'nb_actions_per_visit', 'avg_time_on_site', 'bounce_rate'],
table: { dataset: 'first' },
panels: { dataset: 'second' },
charts: {
nb_visits: [{ 'chart': 'c1', 'name': 'Sessions', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
nb_actions: [{ 'chart': 'c1', 'name': 'Actions', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
nb_actions_per_visit: [{ 'chart': 'c1', 'name': 'Actions per Visit', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
avg_time_on_site: [{ 'chart': 'c1', 'name': 'Avg Session Duration', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
bounce_rate: [{ 'chart': 'c1', 'name': 'Bounce Rate', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
},
},
device: {
name: 'Device',
view: 'device',
first: { dimension: 'deviceType', sort: 'nb_visits', sortorder: 'DESCENDING' },
second: { dimension: 'deviceType', sort: 'nb_visits', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'nb_visits',
selectedmetrics: { 'nb_visits': true, 'nb_actions': true, 'nb_actions_per_visit': true, 'avg_time_on_site': true, 'bounce_rate': true },
metrics: ['nb_visits', 'nb_actions', 'nb_actions_per_visit', 'avg_time_on_site', 'bounce_rate'],
table: { dataset: 'first' },
panels: { dataset: 'second' },
charts: {
nb_visits: [{ 'chart': 'c1', 'name': 'Sessions', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
nb_actions: [{ 'chart': 'c1', 'name': 'Actions', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
nb_actions_per_visit: [{ 'chart': 'c1', 'name': 'Actions per Visit', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
avg_time_on_site: [{ 'chart': 'c1', 'name': 'Avg Session Duration', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
bounce_rate: [{ 'chart': 'c1', 'name': 'Bounce Rate', type: 'bar', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
},
},
browser: {
name: 'Browser',
view: 'browser',
first: { dimension: 'browser', sort: 'nb_visits', sortorder: 'DESCENDING' },
second: { dimension: 'browser', sort: 'nb_visits', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'nb_visits',
selectedmetrics: { 'nb_visits': true, 'nb_actions': true, 'bounce_rate': true },
metrics: ['nb_visits', 'nb_actions', 'bounce_rate'],
table: { dataset: 'first' },
panels: { dataset: 'second' },
charts: {
nb_visits: [{ 'chart': 'c1', 'name': 'Sessions', type: 'progress', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
nb_actions: [{ 'chart': 'c1', 'name': 'Actions', type: 'progress', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
bounce_rate: [{ 'chart': 'c1', 'name': 'Bounce Rate', type: 'progress', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
},
},
os: {
name: 'Operating System',
view: 'os',
first: { dimension: 'os', sort: 'nb_visits', sortorder: 'DESCENDING' },
second: { dimension: 'os', sort: 'nb_visits', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'nb_visits',
selectedmetrics: { 'nb_visits': true, 'nb_actions': true, 'bounce_rate': true },
metrics: ['nb_visits', 'nb_actions', 'bounce_rate'],
table: { dataset: 'first' },
panels: { dataset: 'second' },
charts: {
nb_visits: [{ 'chart': 'c1', 'name': 'Sessions', type: 'progress', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
nb_actions: [{ 'chart': 'c1', 'name': 'Actions', type: 'progress', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
bounce_rate: [{ 'chart': 'c1', 'name': 'Bounce Rate', type: 'progress', dataset: 'first' }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'first' }],
},
},
},
},
channels: {
views: {
all: {
name: 'All',
view: 'all',
first: { dimension: 'date' },
second: { dimension: 'referrerType', sort: 'nb_visits', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'nb_visits',
selectedmetrics: { 'nb_visits': true, 'nb_actions': true, 'nb_actions_per_visit': true, 'avg_time_on_site': true, 'bounce_rate': true },
metrics: ['nb_visits', 'nb_actions', 'nb_actions_per_visit', 'avg_time_on_site', 'bounce_rate'],
table: { dataset: 'second' },
panels: { dataset: 'first' },
charts: {
nb_visits: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions / channel', type: 'pie', dataset: 'second' }],
nb_actions: [{ 'chart': 'c1', 'name': 'Actions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Actions / channel', type: 'pie', dataset: 'second' }],
nb_actions_per_visit: [{ 'chart': 'c1', 'name': 'Actions per Visit', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
avg_time_on_site: [{ 'chart': 'c1', 'name': 'Avg Session Duration', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
bounce_rate: [{ 'chart': 'c1', 'name': 'Bounce Rate', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
organic: {
name: 'Organic',
view: 'organic',
first: { dimension: 'date' },
second: { dimension: 'searchEngine', sort: 'nb_visits', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'nb_visits',
selectedmetrics: { 'nb_visits': true, 'nb_actions': true, 'bounce_rate': true },
metrics: ['nb_visits', 'nb_actions', 'bounce_rate'],
table: { dataset: 'second' },
panels: { dataset: 'first' },
charts: {
nb_visits: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
nb_actions: [{ 'chart': 'c1', 'name': 'Actions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
bounce_rate: [{ 'chart': 'c1', 'name': 'Bounce Rate', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
social: {
name: 'Social',
view: 'social',
first: { dimension: 'date' },
second: { dimension: 'socialNetwork', sort: 'nb_visits', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'nb_visits',
selectedmetrics: { 'nb_visits': true, 'nb_actions': true, 'bounce_rate': true },
metrics: ['nb_visits', 'nb_actions', 'bounce_rate'],
table: { dataset: 'second' },
panels: { dataset: 'first' },
charts: {
nb_visits: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
nb_actions: [{ 'chart': 'c1', 'name': 'Actions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
bounce_rate: [{ 'chart': 'c1', 'name': 'Bounce Rate', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
referral: {
name: 'Referral',
view: 'referral',
first: { dimension: 'date' },
second: { dimension: 'website', sort: 'nb_visits', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'nb_visits',
selectedmetrics: { 'nb_visits': true, 'nb_actions': true, 'bounce_rate': true },
metrics: ['nb_visits', 'nb_actions', 'bounce_rate'],
table: { dataset: 'second' },
panels: { dataset: 'first' },
charts: {
nb_visits: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
nb_actions: [{ 'chart': 'c1', 'name': 'Actions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
bounce_rate: [{ 'chart': 'c1', 'name': 'Bounce Rate', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
campaigns: {
name: 'Campaigns',
view: 'campaigns',
first: { dimension: 'date' },
second: { dimension: 'campaign', sort: 'nb_visits', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'nb_visits',
selectedmetrics: { 'nb_visits': true, 'nb_actions': true, 'bounce_rate': true },
metrics: ['nb_visits', 'nb_actions', 'bounce_rate'],
table: { dataset: 'second' },
panels: { dataset: 'first' },
charts: {
nb_visits: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
nb_actions: [{ 'chart': 'c1', 'name': 'Actions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'pie', dataset: 'second' }],
bounce_rate: [{ 'chart': 'c1', 'name': 'Bounce Rate', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': '_', type: 'bar', dataset: 'second' }],
},
},
},
},
pages: {
views: {
all: {
name: 'All Pages',
view: 'all',
first: { dimension: 'date' },
second: { dimension: 'pageUrl', sort: 'nb_hits', sortorder: 'DESCENDING' },
selectedmetric: 'nb_hits',
selectedmetrics: { 'nb_hits': true, 'nb_visits': true, 'bounce_rate': true, 'avg_time_on_page': true },
metrics: ['nb_hits', 'nb_visits', 'bounce_rate', 'avg_time_on_page'],
table: { dataset: 'second' },
panels: { dataset: 'first' },
charts: {
nb_hits: [{ 'chart': 'c1', 'name': 'Pageviews', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Pages', type: 'progress', dataset: 'second' }],
nb_visits: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Pages', type: 'progress', dataset: 'second' }],
bounce_rate: [{ 'chart': 'c1', 'name': 'Bounce Rate', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Pages', type: 'progress', dataset: 'second' }],
avg_time_on_page: [{ 'chart': 'c1', 'name': 'Avg Time on Page', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Pages', type: 'progress', dataset: 'second' }],
},
},
titles: {
name: 'Page Titles',
view: 'titles',
first: { dimension: 'date' },
second: { dimension: 'pageTitle', sort: 'nb_hits', sortorder: 'DESCENDING' },
selectedmetric: 'nb_hits',
selectedmetrics: { 'nb_hits': true, 'nb_visits': true, 'bounce_rate': true, 'avg_time_on_page': true },
metrics: ['nb_hits', 'nb_visits', 'bounce_rate', 'avg_time_on_page'],
table: { dataset: 'second' },
panels: { dataset: 'first' },
charts: {
nb_hits: [{ 'chart': 'c1', 'name': 'Pageviews', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Pages', type: 'progress', dataset: 'second' }],
nb_visits: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Pages', type: 'progress', dataset: 'second' }],
bounce_rate: [{ 'chart': 'c1', 'name': 'Bounce Rate', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Pages', type: 'progress', dataset: 'second' }],
avg_time_on_page: [{ 'chart': 'c1', 'name': 'Avg Time on Page', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Pages', type: 'progress', dataset: 'second' }],
},
},
entry: {
name: 'Entry Pages',
view: 'entry',
first: { dimension: 'date' },
second: { dimension: 'entryPage', sort: 'nb_visits', sortorder: 'DESCENDING' },
selectedmetric: 'nb_visits',
selectedmetrics: { 'nb_visits': true, 'bounce_rate': true },
metrics: ['nb_visits', 'bounce_rate'],
table: { dataset: 'second' },
panels: { dataset: 'first' },
charts: {
nb_visits: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Entry Pages', type: 'progress', dataset: 'second' }],
bounce_rate: [{ 'chart': 'c1', 'name': 'Bounce Rate', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Entry Pages', type: 'progress', dataset: 'second' }],
},
},
exit: {
name: 'Exit Pages',
view: 'exit',
first: { dimension: 'date' },
second: { dimension: 'exitPage', sort: 'nb_visits', sortorder: 'DESCENDING' },
selectedmetric: 'nb_visits',
selectedmetrics: { 'nb_visits': true },
metrics: ['nb_visits'],
table: { dataset: 'second' },
panels: { dataset: 'first' },
charts: {
nb_visits: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Exit Pages', type: 'progress', dataset: 'second' }],
},
},
},
},
events: {
views: {
categories: {
name: 'Event Categories',
view: 'categories',
first: { dimension: 'date' },
second: { dimension: 'eventCategory', sort: 'nb_visits', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'nb_visits',
selectedmetrics: { 'nb_visits': true, 'nb_actions': true },
metrics: ['nb_visits', 'nb_actions'],
table: { dataset: 'second' },
panels: { dataset: 'first' },
charts: {
nb_visits: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Categories', type: 'bar', dataset: 'second' }],
nb_actions: [{ 'chart': 'c1', 'name': 'Actions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Categories', type: 'pie', dataset: 'second' }],
},
},
actions: {
name: 'Event Actions',
view: 'actions',
first: { dimension: 'date' },
second: { dimension: 'eventAction', sort: 'nb_visits', sortorder: 'DESCENDING', 'allowcomp': true },
selectedmetric: 'nb_visits',
selectedmetrics: { 'nb_visits': true, 'nb_actions': true },
metrics: ['nb_visits', 'nb_actions'],
table: { dataset: 'second' },
panels: { dataset: 'first' },
charts: {
nb_visits: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Actions', type: 'bar', dataset: 'second' }],
nb_actions: [{ 'chart': 'c1', 'name': 'Actions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Top Actions', type: 'pie', dataset: 'second' }],
},
},
},
},
};
// Object for modal form binding (dot rule for ng-include child scope)
$scope.matomoForm = { url: '', token: '', error: null, connecting: false };
// Custom connect function for Matomo (form-based, not OAuth)
$scope.fnMatomoConnect = function () {
$scope.matomoForm.error = null;
$scope.matomoForm.connecting = true;
$http.post('/rest/matomo/connect', {
websiteid: h.siteId(),
url: $scope.matomoForm.url,
token_auth: $scope.matomoForm.token,
}).then(function (res) {
$scope.matomoForm.connecting = false;
if (res.data.error) {
$scope.matomoForm.error = res.data.error;
} else {
$scope.getCustomer();
}
}, function () {
$scope.matomoForm.connecting = false;
$scope.matomoForm.error = 'Connection failed. Please check the URL and try again.';
});
}
// Custom site selector for Matomo
$scope.setMatomoCustomer = function (data) {
$http.get('/rest/matomo/profileid.set', {
params: {
websiteid: h.siteId(),
idsite: data.idsite
}
}).then(function () {
$scope.getReportData();
});
}
$controller('coreapi', { $scope: $scope });
$scope.init();
}
]);
app.controller('copernica', ['$scope', '$timeout', '$http', '$rootScope', '$controller', '$filter', 'h',
function($scope, $timeout, $http, $rootScope, $controller, $filter, h) {
$scope.cfg = {
authurl: 'https://app.pageowl.app/rest/copernica/connect?websiteid=' + h.siteId(),
dateformat: "YYYY-MM-DD",
dimensionprefix: '',
urlbase: 'copernica',
report_endpoint: '/rest/copernica/report',
}
$scope.gloMetrics = h.metrics('copernica');
/* $scope.gloMetrics = {
Placeholder: { name: '', typefilter: 'number', filtercomparator: 0 },
subject: { name: 'Subject', typefilter: 'number', filtercomparator: 0 },
type: { name: 'Type', typefilter: 'string'},
destinations: { name: 'Destinations', typefilter: 'number', filtercomparator: 0 },
abuses: { name: 'Abuses', typefilter: 'number', filtercomparator: 0 },
clicks: { name: 'Clicks', typefilter: 'number', filtercomparator: 0 },
uniqueclicks: { name: 'Unique Clicks', typefilter: 'number', filtercomparator: 0 },
deliveries: { name: 'Deliveries', typefilter: 'number', filtercomparator: 0 },
errors: { name: 'Errors', typefilter: 'number', filtercomparator: 0 },
impressions: { name: 'Impressions', typefilter: 'number', filtercomparator: 0 },
uniqueimpressions: { name: 'Unique Impressions', typefilter: 'number', filtercomparator: 0 },
retries: { name: 'Retries', typefilter: 'number', filtercomparator: 0 },
unsubscribes: { name: 'Unsubscribes', typefilter: 'number', filtercomparator: 0 },
};
*/
//'page_fans','page_fans_country','page_fans_by_like_source','page_fans_gender_age'
$scope.reportconfig = {
mail: {
views: {
publishercampaigns: {
name: 'Publisher Campagnes',
view: 'publishercampaigns',
first: { dimension: 'subject', 'report': 'publishercampaigns' },
second: { dimension: 'title', 'report': 'publishercampaignsclicks' },
third: { dimension: 'title', 'report': 'publishercampaignsimpressions' },
selectedmetric: 'Placeholder',
dimension: 'title',
charts: {
Placeholder: [
{ 'chart': 'c0', 'name': 'Clicks', type: 'line', dataset: 'second', metric: 'clicks', dimension: 'date' },
{ 'chart': 'c1', 'name': 'Impressions', type: 'line', dataset: 'third', metric: 'impressions', dimension: 'date' },
],
},
metrics: [ 'type', 'destinations'],
table: { dataset: 'first' },
},
publishercampaign: {
name: 'Campagne',
view: 'publishercampaign',
hide: true,
first: { dimension: 'title', 'report': 'publishertimestats', filterexpression: $scope.urlfilter },
second: { dimension: 'title', 'report': 'publisherstats', filterexpression: $scope.urlfilter },
selectedmetric: 'Placeholder',
dimension: 'title',
metrics: ['destinations', 'abuses', 'clicks', 'uniqueclicks', 'deliveries', 'errors', 'impressions','uniqueimpressions','retries','unsubscribes'],
panels: { dataset: 'second' },
charts: {
Placeholder: [
{ 'chart': 'c0', 'name': 'Clicks', type: 'line', dataset: 'first', metric: 'clicks', dimension: 'date' },
{ 'chart': 'c1', 'name': 'Errors', type: 'line', dataset: 'first', metric: 'errors', dimension: 'date' },
{ 'chart': 'c2', 'name': 'Impressions', type: 'line', dataset: 'first', metric: 'impressions', dimension: 'date' },
{ 'chart': 'c3', 'name': 'Unsubscribes', type: 'line', dataset: 'first', metric: 'unsubscribes', dimension: 'date' },
],
},
},
mscampaigns: {
name: 'Campagnes',
view: 'mscampaigns',
first: { dimension: 'subject', 'report': 'mscampaigns' },
second: { dimension: 'title', 'report': 'mscampaignsclicks' },
third: { dimension: 'title', 'report': 'mscampaignsimpressions' },
selectedmetric: 'Placeholder',
dimension: 'subject',
charts: {
Placeholder: [
{ 'chart': 'c0', 'name': 'Clicks', type: 'line', dataset: 'second', metric: 'clicks', dimension: 'date' },
{ 'chart': 'c1', 'name': 'Impressions', type: 'line', dataset: 'third', metric: 'impressions', dimension: 'date' },
],
},
metrics: [ 'type', 'destinations'],
table: { dataset: 'first' },
},
mscampaign: {
name: 'Campagne',
view: 'mscampaign',
hide: true,
first: { dimension: 'title', 'report': 'mstimestats', filterexpression: $scope.urlfilter },
second: { dimension: 'title', 'report': 'msstats', filterexpression: $scope.urlfilter },
selectedmetric: 'Placeholder',
dimension: 'title',
metrics: ['destinations', 'abuses', 'clicks', 'uniqueclicks', 'deliveries', 'errors', 'impressions','uniqueimpressions','retries','unsubscribes'],
panels: { dataset: 'second' },
charts: {
Placeholder: [
{ 'chart': 'c0', 'name': 'Clicks', type: 'line', dataset: 'first', metric: 'clicks', dimension: 'date' },
{ 'chart': 'c1', 'name': 'Errors', type: 'line', dataset: 'first', metric: 'errors', dimension: 'date' },
{ 'chart': 'c2', 'name': 'Impressions', type: 'line', dataset: 'first', metric: 'impressions', dimension: 'date' },
{ 'chart': 'c3', 'name': 'Unsubscribes', type: 'line', dataset: 'first', metric: 'unsubscribes', dimension: 'date' },
],
},
},
},
},
}
$controller('coreapi', { $scope: $scope });
$scope.init();
}
]);
/*
The current navigation is too literal. It names the data dimension, not the insight. We'll change it to be benefit-driven.
Current Text Proposed Change Why It's Better
Keywords Keyword Opportunities Focuses on the goal: finding new keyword targets.
Pages Top Content Highlights that these are the most important pages.
Country Geographic Performance Sounds more professional and comprehensive.
Device Device Analytics Aligns with industry-standard terminology.
Clusters Topic Intelligence Elevates the feature from a simple grouping to a strategic insight. This is the key change.
Gaps & Gains Gaps & Gains Clearly communicates finding new opportunities and plugging leaks.
*/
app.controller('searchconsole', ['$scope', '$timeout', '$http', '$rootScope', '$controller', '$filter', 'h',
function ($scope, $timeout, $http, $rootScope, $controller, $filter, h) {
$scope.cfg = {
authurl: 'https://app.marketingtracer.com/rest/searchconsole/connect?websiteid=' + h.siteId(),
dateformat: "YYYY-MM-DD",
dimensionprefix: '',
urlbase: 'searchconsole',
report_endpoint: '/rest/searchconsole/report',
}
if ($rootScope.user.reseller?.config?.authdomain) {
$scope.cfg.authurl = 'https://' + $rootScope.user.reseller.config.authdomain + '/rest/searchconsole/connect?websiteid=' + h.siteId();
}
$scope.search = {
updateFilter: function (query) {
$scope.search.query = query;
},
query: ''
};
$scope.gloMetrics = h.metrics('searchconsole');
$scope.limit = 100;
$scope.impressions = 100;
$scope.samplesizes = [100, 250, 500, 1000, 2500, 5000];
$scope.sampleipressions = [50, 100, 200, 500];
$scope.minimpressions = 100;
$scope.clusterProgress = 0;
$scope.topicMode = 'smart';
$scope.setTopicMode = function (mode) {
$scope.topicMode = mode;
}
$scope.fnSetSampleImpressions = function (impressions) {
$scope.impressions = impressions;
$scope.fnMakeChart($scope.charts[0]);
}
$scope.checkAccessToken = function(data) {
if(data.action == 'connect'){
$scope.modaloverride = true;
$scope.showConnectNudge = true;
}
};
$scope.getClusterPendingState = function(data) {
if(data?.data?.status == 'pending' || data?.data?.status == 'clustering'){
$scope.startClusterPolling();
}
}
$scope.reportconfig = {
campaigns: {
views: {
keywords: {
name: 'Keywords',
view: 'keywords',
first: { dimension: 'date', report: 'searchAnalytics' },
second: { dimension: 'query', report: 'searchAnalytics' },
selectedmetric: 'clicks',
selectedmetrics: { 'clicks': true, 'ctr': true, 'position': true, 'impressions': true },
metrics: ['clicks', 'ctr', 'position', 'impressions'],
table: { dataset: 'second' },
panels: { dataset: 'first' },
charts: {
clicks: [{ 'chart': 'c1', 'name': 'Clicks', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Clicks per keyword', type: 'progress', dataset: 'second' }],
ctr: [{ 'chart': 'c1', 'name': 'Ctr', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Ctr per keyword', type: 'progress', dataset: 'second' }],
position: [{ 'chart': 'c1', 'name': 'Position', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Position per keyword', type: 'progress', dataset: 'second' }],
impressions: [{ 'chart': 'c1', 'name': 'Impressions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Impressions per keyword', type: 'progress', dataset: 'second' }],
},
},
pages: {
name: 'Pages',
view: 'pages',
first: { dimension: 'date', report: 'searchAnalytics' },
second: { dimension: 'page', report: 'searchAnalytics' },
selectedmetric: 'clicks',
selectedmetrics: { 'clicks': true, 'ctr': true, 'position': true, 'impressions': true },
metrics: ['clicks', 'ctr', 'position', 'impressions'],
table: { dataset: 'second' },
panels: { dataset: 'second' },
charts: {
clicks: [{ 'chart': 'c1', 'name': 'Clicks', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Clicks', type: 'progress', dataset: 'second' }],
ctr: [{ 'chart': 'c1', 'name': 'Impressions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Impressions', type: 'progress', dataset: 'second' }],
position: [{ 'chart': 'c1', 'name': 'Position', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Position', type: 'progress', dataset: 'second' }],
impressions: [{ 'chart': 'c1', 'name': 'Impressionsk', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Impressions', type: 'progress', dataset: 'second' }],
},
},
country: {
name: 'Country',
view: 'country',
first: { dimension: 'date', report: 'searchAnalytics' },
second: { dimension: 'country', report: 'searchAnalytics' },
selectedmetric: 'clicks',
selectedmetrics: { 'clicks': true, 'ctr': true, 'position': true, 'impressions': true },
metrics: ['clicks', 'ctr', 'position', 'impressions'],
table: { dataset: 'second' },
panels: { dataset: 'second' },
charts: {
clicks: [{ 'chart': 'c1', 'name': 'Clicks', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Clicks', type: 'bar', dataset: 'second' }],
ctr: [{ 'chart': 'c1', 'name': 'Impressions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Impressions', type: 'bar', dataset: 'second' }],
position: [{ 'chart': 'c1', 'name': 'Position', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Position', type: 'bar', dataset: 'second' }],
impressions: [{ 'chart': 'c1', 'name': 'Impressions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Impressions', type: 'bar', dataset: 'second' }],
},
},
device: {
name: 'Device',
view: 'device',
first: { dimension: 'date', report: 'searchAnalytics' },
second: { dimension: 'device', report: 'searchAnalytics' },
selectedmetric: 'clicks',
selectedmetrics: { 'clicks': true, 'ctr': true, 'position': true, 'impressions': true },
metrics: ['clicks', 'ctr', 'position', 'impressions'],
table: { dataset: 'second' },
panels: { dataset: 'second' },
charts: {
clicks: [{ 'chart': 'c1', 'name': 'Clicks', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Clicks', type: 'bar', dataset: 'second' }],
ctr: [{ 'chart': 'c1', 'name': 'Impressions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Impressions', type: 'bar', dataset: 'second' }],
position: [{ 'chart': 'c1', 'name': 'Position', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Position', type: 'bar', dataset: 'second' }],
impressions: [{ 'chart': 'c1', 'name': 'Impressionsk', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'CPC', type: 'bar', dataset: 'second' }],
},
},
pagekw: {
name: $scope.urlfilter,
hide: true,
view: 'pagekw',
first: { dimension: 'date', report: 'searchAnalytics', filterdimension: "query", filterexpression: $scope.urlfilter, filteroperator: "equals" },
second: { dimension: 'page', report: 'searchAnalytics', filterdimension: "query", filterexpression: $scope.urlfilter, filteroperator: "equals" },
selectedmetric: 'clicks',
selectedmetrics: { 'clicks': true, 'ctr': true, 'position': true, 'impressions': true },
metrics: ['clicks', 'ctr', 'position', 'impressions'],
table: { dataset: 'second' },
panels: { dataset: 'second' },
charts: {
clicks: [{ 'chart': 'c1', 'name': 'Clicks', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Clicks', type: 'progress', dataset: 'second' }],
ctr: [{ 'chart': 'c1', 'name': 'CTR', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'CTR', type: 'progress', dataset: 'second' }],
position: [{ 'chart': 'c1', 'name': 'Position', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Position ', type: 'progress', dataset: 'second' }],
impressions: [{ 'chart': 'c1', 'name': 'Impressions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Impressions', type: 'progress', dataset: 'second' }],
},
},
/*ctr_position: {
name: 'Ctr / position',
view: 'ctr_position',
fullchart: true,
first: { dimension: 'page', report: 'searchAnalytics', 'start': '60DAYSAGO', 'end': 'TODAY', 'limit': $scope.limit },
selectedmetric: 'clicks',
metrics: ['clicks', 'ctr', 'position', 'impressions'],
charts: {
clicks: [{ 'chart': 'c1', 'name': 'Ctr en Position', type: 'bubble', dataset: 'first', date: true, }],
},1
},*/
performancehub: {
name: 'New, Lost and Opprotunities',
view: 'clusters',
fullchart: false,
first: { dimension: 'date', report: 'searchAnalytics', callback:$scope.checkAccessToken },
second: { dimension: 'query', report: 'clusters', 'start': '60DAYSAGO', 'end': 'TODAY', 'limit': $scope.limit, callback:$scope.getClusterPendingState },
third: { dimension: 'query', report: 'searchAnalytics'},
selectedmetric: 'clicks',
metrics: ['clicks', 'ctr', 'position', 'impressions'],
hide: true,
panels: { dataset: 'first' },
selectedmetrics: { 'clicks': true, 'ctr': true, 'position': true, 'impressions': true },
charts: {
clicks: [{ 'chart': 'c1', 'name': 'Clicks', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Clicks per keyword', type: 'progress', dataset: 'third' }],
ctr: [{ 'chart': 'c1', 'name': 'Ctr', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Ctr per keyword', type: 'progress', dataset: 'third' }],
position: [{ 'chart': 'c1', 'name': 'Position', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Position per keyword', type: 'progress', dataset: 'third' }],
impressions: [{ 'chart': 'c1', 'name': 'Impressions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Impressions per keyword', type: 'progress', dataset: 'third' }],
},
},
lost_and_new: {
name: 'New, Lost and Opprotunities',
view: 'lost_and_new',
fullchart: false,
first: { dimension: 'query', report: 'lost_and_new', 'start': '60DAYSAGO', 'end': 'TODAY', 'limit': $scope.limit },
selectedmetric: 'clicks',
metrics: ['clicks', 'ctr', 'position', 'impressions'],
},
},
},
}
$scope.$watch('newData', function () { $scope.fnCalcCtrl(); });
$scope.fnSetLimit = function (limit) {
$scope.view.first.limit = limit;
$scope.getReportData();
}
/* CLustering */
$scope.fnStartClustering = function () {
$scope.analytics.second = { action: 'pending', data: { status: 'pending' } };
$http.get('/rest/searchconsole/cluster_create?websiteid=' + h.siteId()).then(function (response) {
$scope.getReportData();
});
}
// Poll cluster status every 10 seconds when action is pending
$scope.startClusterPolling = function () {
// Prevent multiple polling intervals
if ($scope.clusterPollingInterval) {
$timeout.cancel($scope.clusterPollingInterval);
}
console.log('Cluster polling started.');
var poll = function () {
$http.get('/rest/searchconsole/cluster_get_names?websiteid=' + h.siteId()).then(function (response) {
if (response.data && response.data.data) {
var status = response.data.data.status;
// Ensure analytics.second and its data object exist
$scope.analytics.second = $scope.analytics.second || {};
$scope.analytics.second.data = $scope.analytics.second.data || {};
// Update data on scope
$scope.analytics.second.data.cluster_metrics = response.data.data.cluster_metrics;
$scope.analytics.second.data.status = status;
// Calculate progress: completed clusters / total clusters
if ($scope.analytics.second.data.cluster_metrics) {
var total = $scope.analytics.second.data.cluster_metrics.length;
var completed = $scope.analytics.second.data.cluster_metrics.filter(c => c.status === 'completed').length;
$scope.clusterProgress = total > 0 ? (completed / total) * 100 : 2;
}
// If status is no longer clustering or pending, stop polling
if (status !== 'clustering' && status !== 'pending') {
$timeout.cancel($scope.clusterPollingInterval);
$scope.clusterPollingInterval = null;
console.log('Clustering complete. Polling stopped.');
} else {
// Otherwise, continue polling
$scope.clusterPollingInterval = $timeout(poll, 10000);
}
}
}).catch(function (error) {
console.log('Error polling cluster status:', error);
// Stop polling on error to prevent infinite loops
$timeout.cancel($scope.clusterPollingInterval);
});
};
// Start the first poll immediately
poll();
};
$scope.fnCalcCtrl = function () {
var data = $scope.newData;
var aCtr = {};
var aExpected = {};
$scope.telaag = [];
$scope.tehoog = [];
/* array met cohorten en trc*/
for (var i in data) {
if (data[i].impressions > $scope.minimpressions) {
var cohort = (Math.round(data[i].position / 4));
data[i].cohort = cohort;
aCtr[cohort] = aCtr[cohort] || [];
aCtr[cohort].push(data[i].ctr);
}
}
/* bereken avg en stdev uit cohorten array*/
for (var i in aCtr) {
var aStats = fnStDev(aCtr[i]);
aExpected[i] = aStats;
}
/* match data met verwachte waarden*/
for (var i in data) {
if (data[i].impressions > $scope.minimpressions) {
if (data[i].ctr < (aExpected[data[i].cohort][0] - aExpected[data[i].cohort][1])) {
$scope.telaag.push({ 'data': data[i], 'avg': aExpected[data[i].cohort] });
} else if (data[i].ctr > (aExpected[data[i].cohort][0] + aExpected[data[i].cohort][1])) {
$scope.tehoog.push({ 'data': data[i], 'avg': aExpected[data[i].cohort] });
}
}
}
}
var fnStDev = function (numbersArr) {
//--CALCULATE AVAREGE--
var total = 0;
for (var key in numbersArr)
total += numbersArr[key];
var meanVal = total / numbersArr.length;
//--CALCULATE AVAREGE--
//--CALCULATE STANDARD DEVIATION--
var SDprep = 0;
for (var key in numbersArr)
SDprep += Math.pow((parseFloat(numbersArr[key]) - meanVal), 2);
var SDresult = Math.sqrt(SDprep / numbersArr.length);
//--CALCULATE STANDARD DEVIATION--
return ([meanVal, SDresult]);
}
var standardDeviation = function (values) {
var avg = average(values);
var squareDiffs = values.map(function (value) {
var diff = value - avg;
var sqrDiff = diff * diff;
return sqrDiff;
});
var avgSquareDiff = average(squareDiffs);
var stdDev = Math.sqrt(avgSquareDiff);
return [avg, stdDev];
}
var average = function (data) {
var sum = data.reduce(function (sum, value) {
return sum + value;
}, 0);
var avg = sum / data.length;
return avg;
}
//create bubblechart function
var fnBubbleChart = function (chart, dataset, dimension, metric) {
var newData = [];
for (var i in dataset) {
var cur = dataset[i];
cur.color = $scope.colors[i % 13];
if (cur.impressions > $scope.impressions) {
newData.push(cur);
}
}
$scope.newData = newData;
$scope.pagecharts[chart.chart] = {
data: newData,
type: 'xy',
theme: 'light',
marginLeft: 0,
marginRight: 0,
marginTop: 10,
fontFamily: 'Roboto',
categoryField: 'clicks',
rotate: false,
pathToImages: 'vendor/amchart/images/',
categoryAxis: {
gridPosition: "start",
gridThickness: 0
},
valueAxes: [{
position: "left",
axisAlpha: 0,
labelsEnabled: true,
dashLength: 4,
color: '#dedede',
}],
"graphs": [{
"balloonText": "Impressions:[[impressions]]
position:[[position]]
ctr:[[ctr]]
clicks:[[clicks]]
value:[[page]]",
"bullet": "circle",
"bulletBorderAlpha": 0.2,
"bulletAlpha": 0.8,
"lineAlpha": 0,
"fillAlphas": 0,
"valueField": "clicks",
"xField": "position",
"yField": "ctr",
"maxBulletSize": 40,
"colorField": "color"
}],
}
}
// "Waarom daalt of stijgt het verkeer?": de analyse van de MCP-tool diagnose_traffic_drop.
// Vergelijkt de laatste N dagen (tot 3 dagen geleden, GSC loopt achter) met de N dagen ervoor;
// N volgt de gekozen periode.
$scope.drop = {};
function fnDropDays() {
var d = $rootScope.reportdates || {};
var m = String(d.start).match(/^(\d+)DAYSAGO$/);
if (m) { return parseInt(m[1], 10); }
if (d.start == '1YEARAGO') { return 180; }
if (!isNaN(d.start) && !isNaN(d.end)) { return Math.round((d.end - d.start) / 86400); }
return 28;
}
$scope.fnDrop = function () {
$scope.drop = { open: true, loading: true };
$http.get('/rest/searchconsole/drop', { params: { websiteid: h.siteId(), days: fnDropDays() } }).then(function (res) {
$scope.drop = (res.data && res.data.cb == 'ok') ? { open: true, result: res.data.data } : { open: true, error: true };
}, function () {
$scope.drop = { open: true, error: true };
});
}
$scope.fnDropClose = function () {
$scope.drop = {};
}
// Search Console-sleutels zijn domein + pad zonder protocol (www.example.com/pagina)
$scope.fnDropPath = function (url) {
return String(url || '').replace(/^(https?:\/\/)?[^\/]+/, '') || '/';
}
$scope.fnDropHref = function (url) {
url = String(url || '');
return /^https?:\/\//.test(url) ? url : 'https://' + url;
}
//add bubblechart to parent controller
$controller('coreapi', { $scope: $scope });
$scope.fnAddChart('fnBubbleChart', fnBubbleChart);
$scope.init();
// Start polling immediately if the initial state is pending or clustering
if ($scope.analytics && $scope.analytics.second && ($scope.analytics.second.action === 'pending' || ($scope.analytics.second.data && ($scope.analytics.second.data.status === 'clustering' || $scope.analytics.second.data.status === 'pending')))) {
$scope.startClusterPolling();
}
}
]);
app.controller('youtube', ['$scope', '$timeout', '$http', '$rootScope', '$controller', '$filter', 'h',
function($scope, $timeout, $http, $rootScope, $controller, $filter, h) {
$scope.cfg = {
authurl: 'https://app.pageowl.app/rest/youtube/connect?site_id=' + h.siteId(),
dateformat: "YYYY-MM-DD",
dimensionprefix: '',
urlbase: 'youtube',
report_endpoint: '/rest/youtube/report',
}
$scope.gloMetrics = h.metrics('youtube');
/* $scope.gloMetrics = {
commentCount: { name: 'Clicks', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
subscriberCount: { name: 'Impressions', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
videoCount: { name: 'Date', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
viewCount: { name: 'Avg cost / Click', typefilter: 'number', filtercomparator: 0, 'datemerge': 'avg' },
views: { name: 'Ctr', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
shares: { name: 'Conversions', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
likes: { name: 'Cost', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
dislikes: { name: 'Cost / conversion', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
comments: { name: 'Conversion rate', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
subscribersGained: { name: 'AbsoluteTopImpressionPercentage', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
subscribersLost: { name: 'Conversion value', typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
estimatedMinutesWatched: { name: 'Zoekimpressie aandeel', typefilter: 'number', filtercomparator: 1, 'datemerge': 'sum' },
annotationCloseRate: { name: 'Top Positie', typefilter: 'number', filtercomparator: 2, 'after': '%', 'datemerge': 'avg' },
annotationClickThroughRate: { name: 'ROAS', typefilter: 'number', filtercomparator: 2, 'after': '%', 'datemerge': 'avg' },
averageViewDuration: { name: 'ROAS', typefilter: 'secondsToHHmmss', filtercomparator: 0, 'datemerge': 'avg' },
};
*/
$scope.fnFormatAd = function(d) {
return d.HeadlinePart1 + d.HeadlinePart2 + d.Description + d.CreativeDestinationUrl + d.CreativeFinalUrls;
}
$scope.reportconfig = {
channel: {
views: {
overview: {
name: 'Overview',
view: 'overview',
second: { dimension: 'Date', report: 'analytics', 'allowcomp': true },
third: { dimension: 'insightTrafficSourceType', report: 'analytics', metrics: ['views', 'estimatedMinutesWatched'] },
selectedmetric: 'views',
selectedmetrics: { 'Clicks': true, 'Impressions': true, 'Cost': true, 'AverageCpc': true, 'Ctr': true, 'Conversions': true, 'CostPerConversion': true, 'ConversionRate': true, 'AbsoluteTopImpressionPercentage': true, 'ConversionValue': true, 'SearchImpressionShare': true, 'roas': true },
metrics: ['views', 'shares', 'likes', 'dislikes', 'comments', 'subscribersGained', 'subscribersLost', 'estimatedMinutesWatched', 'annotationCloseRate', 'annotationClickThroughRate', 'averageViewDuration'],
//table: { dataset: 'second' },
panels: { dataset: 'second' },
charts: {
views: [{ 'chart': 'c1', 'name': 'views', type: 'line', dataset: 'second', date: true }, { 'chart': 'c2', 'name': 'Views / Source', 'metric':'views', type: 'bar', dataset: 'third' }],
shares: [{ 'chart': 'c1', 'name': 'Shares', type: 'line', dataset: 'second', date: true }, { 'chart': 'c2', 'name': 'Views / Source', 'metric':'views', type: 'bar', dataset: 'third' }],
likes: [{ 'chart': 'c1', 'name': 'Likes', type: 'line', dataset: 'second', date: true }, { 'chart': 'c2', 'name': 'Views / Source', 'metric':'views', type: 'bar', dataset: 'third' }],
dislikes: [{ 'chart': 'c1', 'name': 'Dislikes', type: 'line', dataset: 'second', date: true }, { 'chart': 'c2', 'name': 'Views / Source', 'metric':'views', type: 'bar', dataset: 'third' }],
comments: [{ 'chart': 'c1', 'name': 'Comments', type: 'line', dataset: 'second', date: true }, { 'chart': 'c2', 'name': 'Views / Source', 'metric':'views', type: 'bar', dataset: 'third' }],
subscribersGained: [{ 'chart': 'c1', 'name': 'Subscribers Gained', type: 'line', dataset: 'second', date: true }, { 'chart': 'c2', 'name': 'Views / Source', 'metric':'views', type: 'bar', dataset: 'third' }],
subscribersLost: [{ 'chart': 'c1', 'name': 'Subscribers Lost', type: 'line', dataset: 'second', date: true }, { 'chart': 'c2', 'name': 'Views / Source', 'metric':'views', type: 'bar', dataset: 'third' }],
estimatedMinutesWatched: [{ 'chart': 'c1', 'name': 'Minutes Watched', type: 'line', dataset: 'second', date: true }, { 'chart': 'c2', 'name': 'Views / Source', 'metric':'views', type: 'bar', dataset: 'third' }],
AbsoluteTopImpressionPercentage: [{ 'chart': 'c1', 'name': 'Average position', type: 'line', dataset: 'second', date: true }, { 'chart': 'c2', 'name': 'Views / Source', 'metric':'views', type: 'bar', dataset: 'third' }],
ConversionValue: [{ 'chart': 'c1', 'name': 'Conversion value', type: 'line', dataset: 'second', date: true },{ 'chart': 'c2', 'name': 'Views / Source', 'metric':'views', type: 'bar', dataset: 'third' }],
},
},
anderekak: {
name: 'Overview',
view: 'anderekak',
first: { dimension: 'Date', report: 'channelstats' },
selectedmetric: 'views',
selectedmetrics: { 'Clicks': true, 'Impressions': true, 'Cost': true, 'AverageCpc': true, 'Ctr': true, 'Conversions': true, 'CostPerConversion': true, 'ConversionRate': true, 'AbsoluteTopImpressionPercentage': true, 'ConversionValue': true, 'SearchImpressionShare': true, 'roas': true },
metrics: ['commentCount', 'subscriberCount', 'videoCount', 'viewCount'],
//table: { dataset: 'second' },
panels: { dataset: 'second' },
charts: {
views: [{ 'chart': 'c1', 'name': 'views', type: 'line', dataset: 'second', date: true }, { 'chart': 'c2', 'name': 'Views / Source', 'metric':'views', type: 'bar', dataset: 'third' }],
shares: [{ 'chart': 'c1', 'name': 'Shares', type: 'line', dataset: 'second', date: true }, { 'chart': 'c2', 'name': 'Views / Source', 'metric':'views', type: 'bar', dataset: 'third' }],
likes: [{ 'chart': 'c1', 'name': 'Likes', type: 'line', dataset: 'second', date: true }, { 'chart': 'c2', 'name': 'Views / Source', 'metric':'views', type: 'bar', dataset: 'third' }],
dislikes: [{ 'chart': 'c1', 'name': 'Dislikes', type: 'line', dataset: 'second', date: true }, { 'chart': 'c2', 'name': 'Views / Source', 'metric':'views', type: 'bar', dataset: 'third' }],
comments: [{ 'chart': 'c1', 'name': 'Comments', type: 'line', dataset: 'second', date: true }, { 'chart': 'c2', 'name': 'Views / Source', 'metric':'views', type: 'bar', dataset: 'third' }],
subscribersGained: [{ 'chart': 'c1', 'name': 'Subscribers Gained', type: 'line', dataset: 'second', date: true }, { 'chart': 'c2', 'name': 'Views / Source', 'metric':'views', type: 'bar', dataset: 'third' }],
subscribersLost: [{ 'chart': 'c1', 'name': 'Subscribers Lost', type: 'line', dataset: 'second', date: true }, { 'chart': 'c2', 'name': 'Views / Source', 'metric':'views', type: 'bar', dataset: 'third' }],
estimatedMinutesWatched: [{ 'chart': 'c1', 'name': 'Minutes Watched', type: 'line', dataset: 'second', date: true }, { 'chart': 'c2', 'name': 'Views / Source', 'metric':'views', type: 'bar', dataset: 'third' }],
AbsoluteTopImpressionPercentage: [{ 'chart': 'c1', 'name': 'Average position', type: 'line', dataset: 'second', date: true }, { 'chart': 'c2', 'name': 'Views / Source', 'metric':'views', type: 'bar', dataset: 'third' }],
ConversionValue: [{ 'chart': 'c1', 'name': 'Conversion value', type: 'line', dataset: 'second', date: true },{ 'chart': 'c2', 'name': 'Views / Source', 'metric':'views', type: 'bar', dataset: 'third' }],
},
},
videos: {
name: 'Ad Groups',
view: 'videos',
first: { dimension: 'Date', report: 'videos' },
//second: { dimension: 'AverageCpm', sort: 'sessions', sortorder: 'DESCENDING', },
selectedmetric: 'Clicks',
metrics: ['Clicks'],
},
},
}
}
$controller('coreapi', { $scope: $scope });
$scope.init();
}
]);
app.controller('googleads', ['$scope', '$timeout', '$http', '$rootScope', '$controller', '$filter', 'h',
function ($scope, $timeout, $http, $rootScope, $controller, $filter, h) {
$scope.cfg = {
authurl: 'https://app.marketingtracer.com/rest/googlexads/connect?websiteid=' + h.siteId(),
dateformat: "YYYY-MM-DD",
dimensionprefix: '',
urlbase: 'googlexads',
switchclient: true,
report_endpoint: '/rest/googlexads/report',
}
if ($rootScope.user.reseller?.config?.authdomain) {
$scope.cfg.authurl = 'https://' + $rootScope.user.reseller.config.authdomain + '/rest/googlexads/connect?websiteid=' + h.siteId();
}
$scope.gloMetrics = h.metrics('googlexads');
/* $scope.gloMetrics = {
Clicks: { name: 'Clicks', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
Impressions: { name: 'Impressions', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
Date: { name: 'Date', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
AverageCpc: { name: 'Avg cost / Click', typefilter: 'number', filtercomparator: 2, 'before': '€' ,'datemerge':'avg'},
Ctr: { name: 'Ctr', typefilter: 'number', filtercomparator: 0, 'after': '%' ,'datemerge':'avg'},
Conversions: { name: 'Conversions', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
Cost: { name: 'Cost', typefilter: 'number', filtercomparator: 2, 'before': '€' ,'datemerge':'sum'},
CostPerConversion: { name: 'Cost / conversion', typefilter: 'number', filtercomparator: 2, 'before': '€' ,'datemerge':'avg'},
ConversionRate: { name: 'Conversion rate', typefilter: 'number', filtercomparator: 0, 'after': '%' ,'datemerge':'avg'},
AbsoluteTopImpressionPercentage: { name: 'AbsoluteTopImpressionPercentage', typefilter: 'number', filtercomparator: 0, 'after': '%' ,'datemerge':'avg'},
ConversionValue: { name: 'Conversion value', typefilter: 'number', filtercomparator: 0, 'before': '€' ,'datemerge':'sum'},
SearchImpressionShare: { name: 'Zoekimpressie aandeel', typefilter: 'number', filtercomparator: 2, 'after': '%' ,'datemerge':'avg'},
SearchAbsoluteTopImpressionShare: { name: 'Top Positie', typefilter: 'number', filtercomparator: 2, 'after': '%' ,'datemerge':'avg'},
roas: { name: 'ROAS', typefilter: 'number', filtercomparator: 2, 'after': '%' ,'datemerge':'avg'},
};*/
$scope.fnFormatAd = function (d) {
return d.HeadlinePart1 + d.HeadlinePart2 + d.Description + d.CreativeDestinationUrl + d.CreativeFinalUrls;
}
$scope.reportconfig = {
campaigns: {
views: {
all: {
name: 'Campaigns',
view: 'all',
first: { dimension: 'Date', report: 'ACCOUNT_PERFORMANCE_REPORT' },
second: { dimension: 'CampaignName', report: 'CAMPAIGN_PERFORMANCE_REPORT', 'allowcomp': true },
selectedmetric: 'Clicks',
selectedmetrics: { 'Clicks': true, 'Impressions': true, 'Cost': true, 'AverageCpc': true, 'Ctr': true, 'Conversions': true, 'CostPerConversion': true, 'ConversionRate': true, 'AbsoluteTopImpressionPercentage': true, 'ConversionValue': true, 'SearchImpressionShare': true, 'roas': true },
metrics: ['Clicks', 'Impressions', 'Cost', 'AverageCpc', 'Ctr', 'Conversions', 'CostPerConversion', 'ConversionRate', 'AbsoluteTopImpressionPercentage', 'ConversionValue', 'roas'],
table: { dataset: 'second' },
panels: { dataset: 'second' },
charts: {
Clicks: [{ 'chart': 'c1', 'name': 'Clicks', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Clicks / Campaign', type: 'progress', dataset: 'second' }],
Impressions: [{ 'chart': 'c1', 'name': 'Impressions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Impressions / Campaign', type: 'progress', dataset: 'second' }],
Cost: [{ 'chart': 'c1', 'name': 'Kosten', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions / Campaign', type: 'progress', dataset: 'second' }],
AverageCpc: [{ 'chart': 'c1', 'name': 'Avg cost / Click', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'CPC / Campaign', type: 'progress', dataset: 'second' }],
Ctr: [{ 'chart': 'c1', 'name': 'Ctr', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Ctr / Campaign', type: 'progress', dataset: 'second' }],
Conversions: [{ 'chart': 'c1', 'name': 'Conversies', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Conversies / Campaign', type: 'progress', dataset: 'second' }],
CostPerConversion: [{ 'chart': 'c1', 'name': 'Cost / conversion', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Cost / conversion / Campaign', type: 'progress', dataset: 'second' }],
ConversionRate: [{ 'chart': 'c1', 'name': 'Conversion rate', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Conversion rate / Campaign', type: 'progress', dataset: 'second' }],
AbsoluteTopImpressionPercentage: [{ 'chart': 'c1', 'name': 'Average position', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Average position / Campaign', type: 'progress', dataset: 'second' }],
ConversionValue: [{ 'chart': 'c1', 'name': 'Conversion value', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Conversion value / Campaign', type: 'progress', dataset: 'second' }],
},
},
adgroups: {
name: 'Ad Groups',
view: 'adgroups',
first: { dimension: 'Date', report: 'ACCOUNT_PERFORMANCE_REPORT' },
second: { dimension: 'AdGroupName', report: 'ADGROUP_PERFORMANCE_REPORT', 'allowcomp': true },
//second: { dimension: 'AverageCpm', sort: 'sessions', sortorder: 'DESCENDING', },
selectedmetric: 'Clicks',
selectedmetrics: { 'Clicks': true, 'Impressions': true, 'Cost': true, 'AverageCpc': true, 'Ctr': true, 'Conversions': true, 'CostPerConversion': true, 'ConversionRate': true, 'AbsoluteTopImpressionPercentage': true, 'ConversionValue': true, 'SearchImpressionShare': true, 'roas': true },
metrics: ['Clicks', 'Impressions', 'Cost', 'AverageCpc', 'Ctr', 'Conversions', 'CostPerConversion', 'ConversionRate', 'AbsoluteTopImpressionPercentage', 'ConversionValue', 'roas'],
table: { dataset: 'second' },
panels: { dataset: 'second' },
charts: {
Clicks: [{ 'chart': 'c1', 'name': 'Clicks', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Clicks / Adgroup', type: 'progress', dataset: 'second' }],
Impressions: [{ 'chart': 'c1', 'name': 'Impressions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Impressions / Adgroup', type: 'progress', dataset: 'second' }],
Cost: [{ 'chart': 'c1', 'name': 'Kosten', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions / Adgroup', type: 'progress', dataset: 'second' }],
AverageCpc: [{ 'chart': 'c1', 'name': 'Avg cost / Click', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'CPC / Adgroup', type: 'progress', dataset: 'second' }],
Ctr: [{ 'chart': 'c1', 'name': 'Ctr', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Ctr / Adgroup', type: 'progress', dataset: 'second' }],
Conversions: [{ 'chart': 'c1', 'name': 'Conversies', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Conversies / Adgroup', type: 'progress', dataset: 'second' }],
CostPerConversion: [{ 'chart': 'c1', 'name': 'Cost / conversion', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Cost / conversion / Adgroup', type: 'progress', dataset: 'second' }],
ConversionRate: [{ 'chart': 'c1', 'name': 'Conversion rate', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Conversion rate / Adgroup', type: 'progress', dataset: 'second' }],
AbsoluteTopImpressionPercentage: [{ 'chart': 'c1', 'name': 'Average position', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Average position / Adgroup', type: 'progress', dataset: 'second' }],
ConversionValue: [{ 'chart': 'c1', 'name': 'Conversion value', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Conversion value / Adgroup', type: 'progress', dataset: 'second' }],
},
},
keywords: {
name: 'Keywords',
view: 'keywords',
first: { dimension: 'Date', report: 'ACCOUNT_PERFORMANCE_REPORT' },
second: { dimension: 'Query', report: 'SEARCH_QUERY_PERFORMANCE_REPORT', 'allowcomp': true },
selectedmetric: 'Clicks',
selectedmetrics: { 'Clicks': true, 'Impressions': true, 'Cost': true, 'AverageCpc': true, 'Ctr': true, 'Conversions': true, 'CostPerConversion': true, 'ConversionRate': true, 'AbsoluteTopImpressionPercentage': true, 'ConversionValue': true, 'roas': true },
metrics: ['Clicks', 'Impressions', 'Cost', 'AverageCpc', 'Ctr', 'Conversions', 'CostPerConversion', 'ConversionRate', 'AbsoluteTopImpressionPercentage', 'ConversionValue', 'roas'],
table: { dataset: 'second' },
panels: { dataset: 'second' },
charts: {
Clicks: [{ 'chart': 'c1', 'name': 'Clicks', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Clicks / Keyword', type: 'bar', dataset: 'second' }],
Impressions: [{ 'chart': 'c1', 'name': 'Impressions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Impressions / Keyword', type: 'bar', dataset: 'second' }],
Cost: [{ 'chart': 'c1', 'name': 'Kosten', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions / Keyword', type: 'bar', dataset: 'second' }],
AverageCpc: [{ 'chart': 'c1', 'name': 'Avg cost / Click', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'CPC / Keyword', type: 'bar', dataset: 'second' }],
Ctr: [{ 'chart': 'c1', 'name': 'Ctr', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Ctr / Keyword', type: 'bar', dataset: 'second' }],
Conversions: [{ 'chart': 'c1', 'name': 'Conversies', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Conversies / Keyword', type: 'bar', dataset: 'second' }],
CostPerConversion: [{ 'chart': 'c1', 'name': 'Cost / conversion', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Cost / conversion / Keyword', type: 'bar', dataset: 'second' }],
ConversionRate: [{ 'chart': 'c1', 'name': 'Conversion rate', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Conversion rate / Keyword', type: 'bar', dataset: 'second' }],
AbsoluteTopImpressionPercentage: [{ 'chart': 'c1', 'name': 'Average position', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Average position / Keyword', type: 'bar', dataset: 'second' }],
ConversionValue: [{ 'chart': 'c1', 'name': 'Conversion value', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Conversion value / Keyword', type: 'bar', dataset: 'second' }],
},
},
// ads: {
// name: 'Ads',
// view: 'ads',
// first: { dimension: 'Date', report: 'ACCOUNT_PERFORMANCE_REPORT', 'allowcomp': true },
// second: { dimension: 'HeadlinePart1,HeadlinePart2,Description,CreativeDestinationUrl,CreativeFinalUrls', function: 'fnFormatAd', report: 'AD_PERFORMANCE_REPORT' },
// selectedmetric: 'Clicks',
// selectedmetrics: { 'Clicks': true, 'Impressions': true, 'Cost': true, 'AverageCpc': true, 'Ctr': true, 'Conversions': true, 'CostPerConversion': true, 'ConversionRate': true, 'AbsoluteTopImpressionPercentage': true, 'ConversionValue': true, 'roas': true },
// metrics: ['Clicks', 'Impressions', 'Cost', 'AverageCpc', 'Ctr', 'Conversions', 'CostPerConversion', 'ConversionRate', 'AbsoluteTopImpressionPercentage', 'ConversionValue', 'roas'],
// table: { dataset: 'second' },
// panels: { dataset: 'second' },
// charts: {
// Clicks: [{ 'chart': 'c1', 'name': 'Clicks', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Clicks / Ad', type: 'bar', dataset: 'second' }],
// Impressions: [{ 'chart': 'c1', 'name': 'Impressions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Impressions / Ad', type: 'bar', dataset: 'second' }],
// Cost: [{ 'chart': 'c1', 'name': 'Kosten', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions / Ad', type: 'bar', dataset: 'second' }],
// AverageCpc: [{ 'chart': 'c1', 'name': 'Avg cost / Click', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'CPC / Ad', type: 'bar', dataset: 'second' }],
// Ctr: [{ 'chart': 'c1', 'name': 'Ctr', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Ctr / Ad', type: 'bar', dataset: 'second' }],
// Conversions: [{ 'chart': 'c1', 'name': 'Conversies', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Conversies / Ad', type: 'bar', dataset: 'second' }],
// CostPerConversion: [{ 'chart': 'c1', 'name': 'Cost / conversion', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Cost / conversion / Ad', type: 'bar', dataset: 'second' }],
// ConversionRate: [{ 'chart': 'c1', 'name': 'Conversion rate', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Conversion rate / Ad', type: 'bar', dataset: 'second' }],
// AbsoluteTopImpressionPercentage: [{ 'chart': 'c1', 'name': 'Average position', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Average position / Ad', type: 'bar', dataset: 'second' }],
// ConversionValue: [{ 'chart': 'c1', 'name': 'Conversion value', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Conversion value / Ad', type: 'bar', dataset: 'second' }],
// },
// },
// conversions: {
// name: 'Conversions',
// view: 'conversions',
// first: { dimension: 'Date', report: 'ACCOUNT_PERFORMANCE_REPORT' },
// second: { dimension: 'ConversionTypeName', report: 'ACCOUNT_PERFORMANCE_REPORT', 'allowcomp': true },
// selectedmetric: 'Conversions',
// selectedmetrics: { 'Conversions': true, 'ConversionValue': true },
// //metrics: ['Conversions', 'CostPerConversion', 'ConversionRate', 'ConversionValue'],
// metrics: ['Conversions','ConversionValue'],
// table: { dataset: 'second' },
// panels: { dataset: 'second' },
// charts: {
// Clicks: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions / Channel', type: 'bar', dataset: 'second' }],
// Impressions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions / Channel', type: 'bar', dataset: 'second' }],
// Cost: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions / Channel', type: 'bar', dataset: 'second' }],
// AverageCpc: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions / Channel', type: 'bar', dataset: 'second' }],
// Ctr: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions / Channel', type: 'bar', dataset: 'second' }],
// Conversions: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions / Channel', type: 'bar', dataset: 'second' }],
// CostPerConversion: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions / Channel', type: 'bar', dataset: 'second' }],
// ConversionRate: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions / Channel', type: 'bar', dataset: 'second' }],
// AbsoluteTopImpressionPercentage: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions / Channel', type: 'bar', dataset: 'second' }],
// ConversionValue: [{ 'chart': 'c1', 'name': 'Sessions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Sessions / Channel', type: 'bar', dataset: 'second' }],
// },
// },
},
}
}
$controller('coreapi', { $scope: $scope });
$scope.init();
}
]);
app.controller('facebookads', ['$scope', '$timeout', '$http', '$rootScope', '$controller', '$filter', 'h',
function($scope, $timeout, $http, $rootScope, $controller, $filter, h) {
$scope.cfg = {
authurl: 'https://app.marketingtracer.com/rest/facebookads/connect?websiteid=' + h.siteId(),
dateformat: "YYYY-MM-DD",
dimensionprefix: '',
urlbase: 'facebookads',
report_endpoint: '/rest/facebookads/report',
}
$scope.gloMetrics = h.metrics('facebookads');
/* $scope.gloMetrics = {
clicks: { name: 'Klikken', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
impressions: { name: 'Impressies', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
cpc: { name: 'cpc', typefilter: 'number', filtercomparator: 2, 'before': '€' ,'datemerge':'avg'},
cpm: { name: 'cpm', typefilter: 'number', filtercomparator: 2, 'before': '€' ,'datemerge':'avg'},
ctr: { name: 'Ctr', typefilter: 'number', filtercomparator: 2, 'after': '%' ,'datemerge':'avg'},
Conversions: { name: 'Conversions', typefilter: 'number', filtercomparator: 0 ,'datemerge':'sum'},
Cost: { name: 'Cost', typefilter: 'number', filtercomparator: 0, 'before': '€' ,'datemerge':'sum'},
CostPerConversion: { name: $filter('translate')('metrics.spend')+' / conversie', typefilter: 'number', filtercomparator: 0, 'before': '€' ,'datemerge':'avg'},
ConversionRate: { name: 'Conversie ratio', typefilter: 'number', filtercomparator: 0, 'after': '%' ,'datemerge':'avg'},
AveragePosition: { name: 'Gemiddelde positie', typefilter: 'number', filtercomparator: 0 ,'datemerge':'avg'},
ConversionValue: { name: 'Totale conversiewaarde', typefilter: 'number', filtercomparator: 0, 'before': '€' ,'datemerge':'sum'},
relevance_score: { name: 'Relevantie Score', typefilter: 'number', filtercomparator: 0, hide: { 'panel': true } ,'datemerge':'avg'},
spend: { name: 'Kosten', typefilter: 'number', filtercomparator: 2, 'before': '€' ,'datemerge':'sum'},
};
*/
$scope.reportconfig = {
campaigns: {
views: {
all: {
name: 'Campagnes',
view: 'all',
viewfilters: ['campaigns'],
first: { dimension: 'date', level: 'account', 'endpoint': 'insights' },
second: { dimension: 'campaign_name', level: 'campaign', 'endpoint': 'insights', 'summary': 'clicks,impressions,cpc,cpm', 'allowcomp': true },
selectedmetric: 'clicks',
selectedmetrics: { 'clicks': true, 'impressions': true, 'cpc': true, 'cpm': true },
metrics: ['clicks', 'impressions', 'cpc', 'cpm','conversion_values','conversions','cost_per_conversion','website_ctr','website_purchase_roas'],
table: { dataset: 'second' },
panels: { dataset: 'second' },
charts: {
clicks: [{ 'chart': 'c1', 'name': $filter('translate')('metrics.clicks') + ' / Day', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': $filter('translate')('metrics.clicks') + ' / Campaign', type: 'bar', dataset: 'second' }],
impressions: [{ 'chart': 'c1', 'name': $filter('translate')('metrics.impressions') + ' / Day', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': $filter('translate')('metrics.impressions') + ' / Campaign', type: 'bar', dataset: 'second' }],
cpc: [{ 'chart': 'c1', 'name': $filter('translate')('metrics.cpc') + ' / Day', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': $filter('translate')('metrics.cpc') + ' / Campaign', type: 'bar', dataset: 'second' }],
cpm: [{ 'chart': 'c1', 'name': $filter('translate')('metrics.cpm') + ' / Day', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': $filter('translate')('metrics.cpm') + ' / Campaign', type: 'bar', dataset: 'second' }],
},
},
adsets: {
name: 'Advertentie Sets',
view: 'adsets',
viewfilters: ['campaigns'],
first: { dimension: 'date', level: 'account', 'endpoint': 'insights', 'summary': 'clicks,impressions,cpc,cpm,spend,ctr', 'allowcomp': true },
second: { dimension: 'adset_name', level: 'adset', endpoint: 'insights' },
third: { dimension: 'date', level: 'account', 'endpoint': 'insights' },
selectedmetric: 'clicks',
selectedmetrics: { 'clicks': true, 'impressions': true, 'cpc': true, 'cpm': true, 'ctr': true, 'spend': true },
metrics: ['clicks', 'impressions', 'cpc', 'cpm', 'ctr', 'spend'],
table: { dataset: 'second' },
panels: { dataset: 'first' },
charts: {
clicks: [{ 'chart': 'c1', 'name': $filter('translate')('metrics.clicks') + ' / Day', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': $filter('translate')('metrics.clicks') + ' / ad set', type: 'bar', dataset: 'second' }],
impressions: [{ 'chart': 'c1', 'name': $filter('translate')('metrics.impressions') + ' / Day', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': $filter('translate')('metrics.impressions') + ' / ad set', type: 'bar', dataset: 'second' }],
cpc: [{ 'chart': 'c1', 'name': $filter('translate')('metrics.cpc') + ' / Day', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': $filter('translate')('metrics.cpc') + ' / ad set', type: 'bar', dataset: 'second' }],
cpm: [{ 'chart': 'c1', 'name': $filter('translate')('metrics.cpm') + ' / Day', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': $filter('translate')('metrics.cpm') + ' / ad set', type: 'bar', dataset: 'second' }],
ctr: [{ 'chart': 'c1', 'name': $filter('translate')('metrics.ctr') + ' / Day', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': $filter('translate')('metrics.ctr') + ' / ad set', type: 'bar', dataset: 'second' }],
spend: [{ 'chart': 'c1', 'name': $filter('translate')('metrics.spend') + ' / Day', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': $filter('translate')('metrics.spend') + ' / ad set', type: 'bar', dataset: 'second' }],
},
},
ads: {
name: 'Advertenties',
view: 'ads',
viewfilters: ['adsets', 'campaigns', 'ads'],
first: { dimension: 'date', level: 'account', 'endpoint': 'insights', 'summary': 'clicks,impressions,cpc,cpm,spend,ctr', 'allowcomp': true },
second: { dimension: 'ad_name', level: 'ad', endpoint: 'insights' },
selectedmetric: 'clicks',
selectedmetrics: { 'clicks': true, 'impressions': true, 'cpc': true, 'cpm': true, 'ctr': true, 'spend': true },
metrics: ['clicks', 'impressions', 'cpc', 'cpm', 'ctr', 'spend'],
table: { dataset: 'second' },
panels: { dataset: 'first' },
charts: {
clicks: [{ 'chart': 'c1', 'name': $filter('translate')('metrics.clicks') + ' / Day', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': $filter('translate')('metrics.clicks') + ' / ad', type: 'bar', dataset: 'second' }],
impressions: [{ 'chart': 'c1', 'name': $filter('translate')('metrics.impressions') + ' / Day', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': $filter('translate')('metrics.impressions') + ' / ad', type: 'bar', dataset: 'second' }],
cpc: [{ 'chart': 'c1', 'name': $filter('translate')('metrics.cpc') + ' / Day', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': $filter('translate')('metrics.cpc') + ' / ad', type: 'bar', dataset: 'second' }],
cpm: [{ 'chart': 'c1', 'name': $filter('translate')('metrics.cpm') + ' / Day', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': $filter('translate')('metrics.cpm') + ' / ad', type: 'bar', dataset: 'second' }],
ctr: [{ 'chart': 'c1', 'name': $filter('translate')('metrics.ctr') + ' / Day', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': $filter('translate')('metrics.ctr') + ' / ad', type: 'bar', dataset: 'second' }],
spend: [{ 'chart': 'c1', 'name': $filter('translate')('metrics.spend') + ' / Day', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': $filter('translate')('metrics.spend') + ' / ad', type: 'bar', dataset: 'second' }],
},
},
age: {
name: 'Leeftijd',
view: 'age',
viewfilters: ['adsets', 'campaigns', 'ads'],
first: { level: 'account', 'endpoint': 'insights', 'breakdowns': 'age', 'dimension': 'date', 'dimension2': 'age', 'summary': 'clicks,impressions,cpc,cpm', 'allowcomp': true },
second: { level: 'account', 'endpoint': 'insights', 'breakdowns': 'age', 'dimension': 'age' },
selectedmetric: 'clicks',
selectedmetrics: { 'clicks': true, 'impressions': true, 'cpc': true, 'cpm': true },
metrics: ['clicks', 'impressions', 'cpc', 'cpm'],
table: { dataset: 'second' },
panels: { dataset: 'first' },
charts: {
clicks: [{ 'chart': 'c1', 'name': 'Klikken, leeftijd en tijd', type: 'linemulti', dataset: 'first', date: true, 'graphs': ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'] }, { 'chart': 'c2', 'name': $filter('translate')('metrics.clicks') + ' / age group', type: 'bar', dataset: 'second' }],
impressions: [{ 'chart': 'c1', 'name': 'Impressies,leeftijd en tijd', type: 'linemulti', dataset: 'first', date: true, 'graphs': ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'] }, { 'chart': 'c2', 'name': $filter('translate')('metrics.impressions') + ' / age group', type: 'bar', dataset: 'second' }],
cpc: [{ 'chart': 'c1', 'name': 'CPC,leeftijd en tijd', type: 'linemulti', dataset: 'first', date: true, 'graphs': ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'] }, { 'chart': 'c2', 'name': $filter('translate')('metrics.cpm') + ' / age group', type: 'bar', dataset: 'second' }],
cpm: [{ 'chart': 'c1', 'name': 'CPM,leeftijd en tijd', type: 'linemulti', dataset: 'first', date: true, 'graphs': ['18-24', '25-34', '35-44', '45-54', '55-64', '65+'] }, { 'chart': 'c2', 'name': $filter('translate')('metrics.cpc') + ' / age group', type: 'bar', dataset: 'second' }],
},
},
gender: {
name: 'Geslacht',
view: 'gender',
viewfilters: ['adsets', 'campaigns', 'ads'],
first: { level: 'account', 'endpoint': 'insights', 'breakdowns': 'gender', 'dimension': 'date', 'dimension2': 'gender', 'summary': 'clicks,impressions,cpc,cpm', 'allowcomp': true },
second: { level: 'account', 'endpoint': 'insights', 'breakdowns': 'gender', 'dimension': 'gender' },
selectedmetric: 'clicks',
selectedmetrics: { 'clicks': true, 'impressions': true, 'cpc': true, 'cpm': true },
metrics: ['clicks', 'impressions', 'cpc', 'cpm'],
table: { dataset: 'second' },
panels: { dataset: 'first' },
charts: {
clicks: [{ 'chart': 'c1', 'name': 'Klikken, geslacht per dag', type: 'linemulti', dataset: 'first', date: true, 'graphs': ['male', 'female'] }, { 'chart': 'c2', 'name': $filter('translate')('metrics.clicks') + ' / Gender', type: 'bar', dataset: 'second' }],
impressions: [{ 'chart': 'c1', 'name': 'Impressies, geslacht per dag', type: 'linemulti', dataset: 'first', date: true, 'graphs': ['male', 'female'] }, { 'chart': 'c2', 'name': $filter('translate')('metrics.impressions') + ' / Gender', type: 'bar', dataset: 'second' }],
cpc: [{ 'chart': 'c1', 'name': 'CPC, geslacht per dag', type: 'linemulti', dataset: 'first', date: true, 'graphs': ['male', 'female'] }, { 'chart': 'c2', 'name': $filter('translate')('metrics.cpc') + ' / Gender', type: 'bar', dataset: 'second' }],
cpm: [{ 'chart': 'c1', 'name': 'CPM, geslacht per dag', type: 'linemulti', dataset: 'first', date: true, 'graphs': ['male', 'female'] }, { 'chart': 'c2', 'name': $filter('translate')('metrics.cpm') + ' / Gender', type: 'bar', dataset: 'second' }],
},
},
},
},
}
$controller('coreapi', { $scope: $scope });
$scope.init();
}
]);
app.controller('facebookinsights', ['$scope', '$timeout', '$http', '$rootScope', '$controller', '$filter', 'h',
function($scope, $timeout, $http, $rootScope, $controller, $filter, h) {
$scope.cfg = {
authurl: 'https://app.marketingtracer.com/rest/facebookinsights/connect?websiteid=' + h.siteId(),
dateformat: "YYYY-MM-DD",
dimensionprefix: '',
urlbase: 'facebookinsights',
report_endpoint: '/rest/facebookinsights/report',
}
$scope.gloMetrics = h.metrics('facebookinsights');
/* $scope.gloMetrics = {
fans: { name: 'Fans', typefilter: 'number', filtercomparator: 0 },
interacties: { name: 'Interactions', typefilter: 'number', filtercomparator: 0 },
bereik: { name: 'Bereik', typefilter: 'number', filtercomparator: 0 },
};*/
//'page_fans','page_fans_country','page_fans_by_like_source','page_fans_gender_age'
$scope.reportconfig = {
page: {
views: {
fans: {
name: 'Fans',
view: 'fans',
first: { 'report': 'timeline', 'endpoint': 'insights', 'dimension': 'date' },
second: { 'report': 'fans', 'endpoint': 'insights', 'dimension': 'date' },
selectedmetric: 'fans',
metrics: [],
charts: {
fans: [
{ 'chart': 'c1', 'name': 'Total fans', type: 'line', dataset: 'page_fans_total', date: true, 'dimension': 'date', 'metric': 'change' },
{ 'chart': 'c2', 'dimension2': 'type', 'name': 'Lost and new fans', type: 'linemulti', dataset: 'page_fans_delta', date: true, 'dimension': 'date', 'metric': 'change', 'graphs': ['page_fan_removes', 'page_fans_adds_paid', 'page_fans_adds_unpaid'] },
{ 'chart': 'c3', 'name': 'Female fans / age ', type: 'bar', dataset: 'page_fans_female_age', 'dimension': 'key', 'metric': 'val' },
{ 'chart': 'c4', 'name': 'Male fans / age ', type: 'bar', dataset: 'page_fans_male_age', 'dimension': 'key', 'metric': 'val' },
{ 'chart': 'c5', 'name': 'Distribution Male and Female fans', type: 'pie', dataset: 'page_fans_age', 'dimension': 'key', 'metric': 'val' },
],
},
},
engagements: {
name: 'Interactie',
view: 'engagements',
first: { 'report': 'timeline', 'endpoint': 'insights', 'dimension': 'date' },
second: { 'report': 'engagements', 'endpoint': 'insights', 'dimension': 'date' },
selectedmetric: 'interacties',
metrics: [],
charts: {
interacties: [
{ 'chart': 'c1', 'dimension2': 'type', 'name': 'Total interactions / day', type: 'linemulti', dataset: 'engagements', date: true, 'dimension': 'date', 'metric': 'value', 'graphs': ['engagements'] },
{ 'chart': 'c2', 'dimension2': 'type', 'name': 'Interactions / type', type: 'linemulti', dataset: 'engagements', date: true, 'dimension': 'date', 'metric': 'value', 'graphs': ['engagements_comments', 'engagements_likes', 'engagements_links', 'engagements_others'] },
{ 'chart': 'c3', 'name': 'Female interactions / age ', type: 'bar', dataset: 'page_content_activity_female_age', 'dimension': 'key', 'metric': 'val' },
{ 'chart': 'c4', 'name': 'Male interactions / age ', type: 'bar', dataset: 'page_content_activity_male_age', 'dimension': 'key', 'metric': 'val' },
{ 'chart': 'c5', 'name': 'Interactions / age ', type: 'pie', dataset: 'page_content_activity_age', 'dimension': 'key', 'metric': 'val' },
],
},
},
reach: {
name: 'Bereik',
view: 'reach',
first: { 'report': 'timeline', 'endpoint': 'insights', 'dimension': 'date' },
second: { 'report': 'reach', 'endpoint': 'insights', 'dimension': 'date' },
selectedmetric: 'bereik',
metrics: [],
charts: {
bereik: [
{ 'chart': 'c1', 'name': 'Total bereik', type: 'line', dataset: 'page_impressions', date: true, 'dimension': 'date', 'metric': 'change' },
{ 'chart': 'c2', 'dimension2': 'type', 'name': 'Bereik organisch en betaald', type: 'linemulti', dataset: 'page_impressions_type', date: true, 'dimension': 'date', 'metric': 'value', 'graphs': ['page_impressions_paid', 'page_impressions_organic'] },
{ 'chart': 'c3', 'name': 'Female reach / age ', type: 'bar', dataset: 'page_impressions_female_age', 'dimension': 'key', 'metric': 'val' },
{ 'chart': 'c4', 'name': 'Male reach / age ', type: 'bar', dataset: 'page_impressions_male_age', 'dimension': 'key', 'metric': 'val' },
{ 'chart': 'c5', 'name': 'Reach / gender', type: 'pie', dataset: 'page_impressions_age', 'dimension': 'key', 'metric': 'val' },
],
},
},
posts: {
name: 'Posts',
view: 'posts',
first: { 'report': 'posts' },
selectedmetric: 'posts',
metrics: [],
charts: {
},
},
},
},
}
$controller('coreapi', { $scope: $scope });
$scope.init();
}
]);
app.controller('instagram', ['$scope', '$timeout', '$http', '$rootScope', '$controller', '$filter', 'h',
function($scope, $timeout, $http, $rootScope, $controller, $filter, h) {
$scope.cfg = {
authurl: 'https://app.pageowl.app/rest/instagram/connect?websiteid=' + h.siteId(),
dateformat: "YYYY-MM-DD",
dimensionprefix: '',
urlbase: 'instagram',
report_endpoint: '/rest/instagram/report',
}
$scope.gloMetrics = h.metrics('instagram');
/*$scope.gloMetrics = {
impressions: { name: 'Fans', typefilter: 'number', filtercomparator: 0 },
reach: { name: 'Interactions', typefilter: 'number', filtercomparator: 0 },
follower_count: { name: 'Bereik', typefilter: 'number', filtercomparator: 0 },
profile_views: { name: 'Bereik', typefilter: 'number', filtercomparator: 0 },
website_clicks: { name: 'Bereik', typefilter: 'number', filtercomparator: 0 },
email_contacts: { name: 'Bereik', typefilter: 'number', filtercomparator: 0 },
get_directions_clicks: { name: 'Bereik', typefilter: 'number', filtercomparator: 0 },
phone_call_clicks: { name: 'Bereik', typefilter: 'number', filtercomparator: 0 },
text_message_clicks: { name: 'Bereik', typefilter: 'number', filtercomparator: 0 },
};*/
//'page_fans','page_fans_country','page_fans_by_like_source','page_fans_gender_age'
$scope.reportconfig = {
page: {
views: {
insights: {
name: 'insights',
view: 'insights',
first: { 'report': 'timeline', 'endpoint': 'insights', 'dimension': 'date' },
//second: { 'report': 'fans', 'endpoint': 'insights', 'dimension': 'date' },
selectedmetric: 'impressions',
metrics: ['impressions', 'reach', 'follower_count'],
panels: { dataset: 'first' },
charts: {
impressions: [
{ 'chart': 'c1', 'name': 'Follower count', type: 'line', dataset: 'follower_count', date: true, 'dimension': 'date', 'metric': 'change' },
{ 'chart': 'c2', 'name': 'Reach', type: 'line', dataset: 'reach', date: true, 'dimension': 'date', 'metric': 'change' },
{ 'chart': 'c3', 'name': 'Impressions', type: 'line', dataset: 'impressions', date: true, 'dimension': 'date', 'metric': 'change' },
],
},
},
engagements: {
name: 'Interactie',
view: 'engagements',
first: { 'report': 'timeline', 'endpoint': 'insights', 'dimension': 'date' },
//second: { 'report': 'engagements', 'endpoint': 'insights', 'dimension': 'date' },
selectedmetric: 'profile_views',
metrics: ['profile_views', 'website_clicks', 'email_contacts', 'get_directions_clicks', 'phone_call_clicks', 'text_message_clicks'],
panels: { dataset: 'first' },
charts: {
profile_views: [
{ 'chart': 'c1', 'name': 'Profile views', type: 'line', dataset: 'profile_views', date: true, 'dimension': 'date', 'metric': 'change' },
{ 'chart': 'c2', 'name': 'Website clicks', type: 'line', dataset: 'website_clicks', date: true, 'dimension': 'date', 'metric': 'change' },
{ 'chart': 'c3', 'name': 'Email contacts', type: 'line', dataset: 'email_contacts', date: true, 'dimension': 'date', 'metric': 'change' },
],
},
},
audience: {
name: 'Bereik',
view: 'audience',
//first: { 'report': 'timeline', 'endpoint': 'insights', 'dimension': 'date' },
second: { 'report': 'audience', 'endpoint': 'insights', 'dimension': 'date' },
selectedmetric: 'bereik',
metrics: [],
charts: {
bereik: [
{ 'chart': 'c1', 'name': 'Female audience / age ', type: 'bar', dataset: 'audience_gender_female_age', 'dimension': 'key', 'metric': 'val' },
{ 'chart': 'c2', 'name': 'Male audience / age ', type: 'bar', dataset: 'audience_gender_male_age', 'dimension': 'key', 'metric': 'val' },
{ 'chart': 'c3', 'name': 'Gender', type: 'pie', dataset: 'audience_gender', 'dimension': 'type', 'metric': 'val' },
],
},
},
posts: {
name: 'Posts',
view: 'posts',
first: { 'report': 'posts' },
selectedmetric: 'posts',
metrics: [],
},
stories: {
name: 'Stories',
view: 'stories',
first: { 'report': 'stories' },
selectedmetric: 'stories',
metrics: [],
},
},
},
}
$controller('coreapi', { $scope: $scope });
$scope.init();
}
]);
app.controller("linkedin", [
"$scope",
"$timeout",
"$http",
"$rootScope",
"$controller",
"$filter",
"h",
function ($scope, $timeout, $http, $rootScope, $controller, $filter, h) {
$scope.cfg = {
authurl: "https://app.pageowl.app/rest/linkedin/connect?websiteid=" + h.siteId(),
dateformat: "YYYY-MM-DD",
dimensionprefix: "",
urlbase: "linkedin",
report_endpoint: "/rest/linkedin/report",
};
$scope.gloMetrics = h.metrics("linkedin");
// Added pageViews and uniqueVisitors to global metrics
$scope.gloMetrics["pageViews"] = { typefilter: "number", filtercomparator: 0, datemerge: "sum" };
$scope.gloMetrics["uniqueVisitors"] = { typefilter: "number", filtercomparator: 0, datemerge: "sum" };
//'page_fans','page_fans_country','page_fans_by_like_source','page_fans_gender_age'
$scope.reportconfig = {
page: {
views: {
interactions: {
name: "Interactions",
view: "interactions",
second: { dimension: "title", report: "followerstatistics-time" },
third: { dimension: "title", report: "sharestatistics" },
selectedmetric: "likeCount",
metrics: ["likeCount", "shareCount", "engagement", "clickCount", "impressionCount", "commentCount"],
panels: { dataset: "third" },
charts: {
likeCount: [
{ chart: "c0", name: "Likes / days", type: "line", dataset: "third", dimension: "date" },
{ chart: "c1", name: "New followers by type", type: "pie", dataset: "newfollowerdist", dimension: "key", metric: "val" },
{ chart: "c2", metric: "abc", dimension2: "type", name: "Lost and new followers", type: "linemulti", dataset: "newfollowerdelta", date: true, dimension: "date", metric: "value", graphs: ["Organic", "Paid"] },
],
shareCount: [
{ chart: "c0", name: "Shares / Day", type: "line", dataset: "third", dimension: "date" },
{ chart: "c1", name: "New followers by type", type: "pie", dataset: "newfollowerdist", dimension: "key", metric: "val" },
{ chart: "c2", dimension2: "type", name: "Lost and new followers", type: "linemulti", dataset: "newfollowerdelta", date: true, dimension: "date", metric: "value", graphs: ["Organic", "Paid"] },
],
engagement: [
{ chart: "c0", name: "Engagement / day", type: "line", dataset: "third", dimension: "date" },
{ chart: "c1", name: "New followers by type", type: "pie", dataset: "newfollowerdist", dimension: "key", metric: "val" },
{ chart: "c2", dimension2: "type", name: "Lost and new followers", type: "linemulti", dataset: "newfollowerdelta", date: true, dimension: "date", metric: "value", graphs: ["Organic", "Paid"] },
],
clickCount: [
{ chart: "c0", name: "Clicks / day", type: "line", dataset: "third", dimension: "date" },
{ chart: "c1", name: "New followers by type", type: "pie", dataset: "newfollowerdist", dimension: "key", metric: "val" },
{ chart: "c2", dimension2: "type", name: "Lost and new followers", type: "linemulti", dataset: "newfollowerdelta", date: true, dimension: "date", metric: "value", graphs: ["Organic", "Paid"] },
],
impressionCount: [
{ chart: "c0", name: "Impressions / day", type: "line", dataset: "third", dimension: "date" },
{ chart: "c1", name: "New followers by type", type: "pie", dataset: "newfollowerdist", dimension: "key", metric: "val" },
{ chart: "c2", dimension2: "type", name: "Lost and new followers", type: "linemulti", dataset: "newfollowerdelta", date: true, dimension: "date", metric: "value", graphs: ["Organic", "Paid"] },
],
commentCount: [
{ chart: "c0", name: "Comments / day", type: "line", dataset: "third", dimension: "date" },
{ chart: "c1", name: "New followers by type", type: "pie", dataset: "newfollowerdist", dimension: "key", metric: "val" },
{ chart: "c2", dimension2: "type", name: "Lost and new followers", type: "linemulti", dataset: "newfollowerdelta", date: true, dimension: "date", metric: "value", graphs: ["Organic", "Paid"] },
],
},
},
network: {
name: "Network",
view: "network",
first: { dimension: "title", report: "networksize" },
second: { dimension: "title", report: "followerstatistics" },
selectedmetric: "val",
metrics: ["val"],
table: { dataset: "region" },
charts: {
val: [
{ chart: "c0", name: "Followers / Staff size", type: "bar", dataset: "staffcount", dimension: "key", metric: "val" },
{ chart: "c1", name: "Followers / Role", type: "pie", dataset: "seniority", dimension: "key", metric: "val" },
{ chart: "c2", name: "Followers / Country", type: "progress", dataset: "country", dimension: "key", metric: "val" },
{ chart: "c3", name: "Folowers / Industry", type: "progress", dataset: "industry", dimension: "key", metric: "val" },
{ chart: "c4", name: "Folowers / Function", type: "progress", dataset: "function", dimension: "key", metric: "val" },
],
},
},
// NEW VIEW ADDED FOR VISITORS
visitors: {
hide: true,
name: "Visitors",
view: "visitors",
first: { dimension: "title", report: "visitorstatistics" },
selectedmetric: "val",
metrics: ["pageViews", "uniqueVisitors"],
panels: { dataset: "first" },
table: { dataset: "country" },
charts: {
val: [
{ chart: "c0", name: "Visitors / Seniority", type: "pie", dataset: "seniority", dimension: "key", metric: "val" },
{ chart: "c1", name: "Visitors / Industry", type: "bar", dataset: "industry", dimension: "key", metric: "val" },
{ chart: "c2", name: "Visitors / Staff Size", type: "progress", dataset: "staffcount", dimension: "key", metric: "val" },
],
},
},
posts: {
name: "Posts",
view: "posts",
first: { dimension: "title", report: "posts" },
selectedmetric: "Placeholder",
dimension: "title",
metrics: ["open_rate", "click_rate", "emails_sent", "unsubscribed", "bounces", "total_orders", "total_revenue"],
panels: { dataset: "stats" },
charts: {
Placeholder: [
{ chart: "c0", name: "Emails sent", type: "line", dataset: "timeseries", metric: "emails_sent", dimension: "date" },
{ chart: "c1", name: "Emails opened", type: "line", dataset: "timeseries", metric: "unique_opens", dimension: "date" },
{ chart: "c2", name: "Clicks", type: "line", dataset: "timeseries", metric: "recipient_clicks", dimension: "date" },
],
},
},
personalposts: {
hide: true,
name: "Personal Posts",
view: "personalposts",
hide: true,
first: { dimension: "title", report: "personalposts" },
selectedmetric: "Placeholder",
dimension: "title",
metrics: ["open_rate", "click_rate", "emails_sent", "unsubscribed", "bounces", "total_orders", "total_revenue"],
panels: { dataset: "stats" },
charts: {
Placeholder: [
{ chart: "c0", name: "Emails sent", type: "line", dataset: "timeseries", metric: "emails_sent", dimension: "date" },
{ chart: "c1", name: "Emails opened", type: "line", dataset: "timeseries", metric: "unique_opens", dimension: "date" },
{ chart: "c2", name: "Clicks", type: "line", dataset: "timeseries", metric: "recipient_clicks", dimension: "date" },
],
},
},
},
},
};
$controller("coreapi", { $scope: $scope });
$scope.init();
},
]);
app.controller('linkedinxads', ['$scope', '$timeout', '$http', '$rootScope', '$controller', '$filter', 'h',
function($scope, $timeout, $http, $rootScope, $controller, $filter, h) {
$scope.cfg = {
authurl: 'https://app.pageowl.app/rest/linkedinxads/connect?websiteid=' + h.siteId(),
dateformat: "YYYY-MM-DD",
dimensionprefix: '',
urlbase: 'linkedinxads',
report_endpoint: '/rest/linkedinxads/report',
}
$scope.gloMetrics = h.metrics('linkedinxads');
/*
$scope.gloMetrics = {
impressions: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
clicks: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
oneClickLeads: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
costInLocalCurrency: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
externalWebsiteConversions: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
costInLocalCurrency: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
networksize: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
networksizeexternal: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
networksizeinternal: { typefilter: 'number', filtercomparator: 0, 'datemerge': 'sum' },
};
*/
//'page_fans','page_fans_country','page_fans_by_like_source','page_fans_gender_age'
$scope.reportconfig = {
campaigns: {
views: {
all: {
name: 'Campagnes',
view: 'all',
first: { dimension: 'date', report: 'campaigns-time' },
second: { dimension: 'pivotValues', report: 'campaigns-all' },
selectedmetric: 'impressions' ,
selectedmetrics: { 'impressions': true, 'clicks': true, 'oneClickLeads': true, 'costInLocalCurrency': true, 'externalWebsiteConversions': true },
metrics: ['impressions', 'clicks', 'oneClickLeads','costInLocalCurrency','externalWebsiteConversions'],
panels: { dataset: 'second' },
table: { dataset: 'second' },
charts: {
impressions: [{ 'chart': 'c1', 'name': $filter('translate')('metrics.impressions')+' / Day', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': $filter('translate')('metrics.impressions')+' / Campaign', type: 'bar', dataset: 'second' }],
clicks: [{ 'chart': 'c1', 'name': $filter('translate')('metrics.clicks')+' / Day', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': $filter('translate')('metrics.clicks')+' / Campaign', type: 'bar', dataset: 'second' }],
oneClickLeads: [{ 'chart': 'c1', 'name': $filter('translate')('metrics.clicks')+' / Day', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': $filter('translate')('metrics.clicks')+' / Campaign', type: 'bar', dataset: 'second' }],
costInLocalCurrency: [{ 'chart': 'c1', 'name': $filter('translate')('metrics.impressions')+' / Day', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': $filter('translate')('metrics.impressions')+' / Campaign', type: 'bar', dataset: 'second' }],
externalWebsiteConversions: [{ 'chart': 'c1', 'name': $filter('translate')('metrics.cpc')+' / Day', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': $filter('translate')('metrics.cpc')+' / Campaign', type: 'bar', dataset: 'second' }],
cpm: [{ 'chart': 'c1', 'name': $filter('translate')('metrics.cpm')+' / Day', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': $filter('translate')('metrics.cpm')+' / Campaign', type: 'bar', dataset: 'second' }],
},
},
network: {
name: 'Campagnes',
view: 'network',
//first: { dimension: 'title', 'report': 'networksize' },
second: { dimension: 'title', 'report': 'followerstatistics' },
selectedmetric: 'val',
metrics: ['val'],
table: { dataset: 'region' },
charts: {
val: [
{ 'chart': 'c0', 'name': 'Followers / Staff size', type: 'bar', dataset: 'staffcount', 'dimension': 'key', 'metric': 'val' },
{ 'chart': 'c1', 'name': 'Followers / Role', type: 'pie', dataset: 'seniority', 'dimension': 'key', 'metric': 'val' },
{ 'chart': 'c2', 'name': 'Followers / Country', type: 'progress', dataset: 'country', 'dimension': 'key', 'metric': 'val' },
{ 'chart': 'c3', 'name': 'Folowers / Industry', type: 'progress', dataset: 'industry', 'dimension': 'key', 'metric': 'val' },
{ 'chart': 'c4', 'name': 'Folowers / Function', type: 'progress', dataset: 'function', 'dimension': 'key', 'metric': 'val' },
],
},
},
posts: {
name: 'Campagne',
view: 'posts',
first: { dimension: 'title', 'report': 'posts', },
selectedmetric: 'Placeholder',
dimension: 'title',
metrics: ['open_rate', 'click_rate', 'emails_sent', 'unsubscribed', 'bounces', 'total_orders', 'total_revenue'],
panels: { dataset: 'stats' },
charts: {
Placeholder: [
{ 'chart': 'c0', 'name': 'Emails sent', type: 'line', dataset: 'timeseries', metric: 'emails_sent', dimension: 'date' },
{ 'chart': 'c1', 'name': 'Emails opened', type: 'line', dataset: 'timeseries', metric: 'unique_opens', dimension: 'date' },
{ 'chart': 'c2', 'name': 'Clicks', type: 'line', dataset: 'timeseries', metric: 'recipient_clicks', dimension: 'date' },
],
},
},
personalposts: {
hide: true,
name: 'Campagne',
view: 'personalposts',
hide: true,
first: { dimension: 'title', 'report': 'personalposts', },
selectedmetric: 'Placeholder',
dimension: 'title',
metrics: ['open_rate', 'click_rate', 'emails_sent', 'unsubscribed', 'bounces', 'total_orders', 'total_revenue'],
panels: { dataset: 'stats' },
charts: {
Placeholder: [
{ 'chart': 'c0', 'name': 'Emails sent', type: 'line', dataset: 'timeseries', metric: 'emails_sent', dimension: 'date' },
{ 'chart': 'c1', 'name': 'Emails opened', type: 'line', dataset: 'timeseries', metric: 'unique_opens', dimension: 'date' },
{ 'chart': 'c2', 'name': 'Clicks', type: 'line', dataset: 'timeseries', metric: 'recipient_clicks', dimension: 'date' },
],
},
},
},
},
}
$controller('coreapi', { $scope: $scope });
$scope.init();
}
]);
app.controller('mailchimp', ['$scope', '$timeout', '$http', '$rootScope', '$controller', '$filter', 'h',
function($scope, $timeout, $http, $rootScope, $controller, $filter, h) {
$scope.cfg = {
authurl: 'https://app.marketingtracer.com/rest/mailchimp/connect?websiteid=' + h.siteId(),
dateformat: "YYYY-MM-DD",
dimensionprefix: '',
urlbase: 'mailchimp',
report_endpoint: '/rest/mailchimp/report',
}
$scope.gloMetrics = h.metrics('mailchimp');
/*
$scope.gloMetrics = {
Placeholder: { name: '', typefilter: 'number', filtercomparator: 0 },
member_count: { name: 'Aanmeldingen', typefilter: 'number', filtercomparator: 0 },
list_rating: { name: 'Rating', typefilter: 'number', filtercomparator: 0 },
click_rate: { name: 'Klik Ratio', typefilter: 'number', filtercomparator: 0, 'after': '%' },
open_rate: { name: 'Open Ratio', typefilter: 'number', filtercomparator: 0, 'after': '%' },
emails_sent: { name: 'Email verstuurd', typefilter: 'number', filtercomparator: 0 },
send_time: { name: 'Verstuurd op', typefilter: 'mcdate', filtercomparator: 0 },
unsubscribed: { name: 'Afmeldingen', typefilter: 'number', filtercomparator: 0 },
unsubscribe_count: { name: 'Afmeldingen', typefilter: 'number', filtercomparator: 0 },
cleaned_count: { name: 'Verwijderd', typefilter: 'number', filtercomparator: 0 },
bounces: { name: 'Bounce', typefilter: 'number', filtercomparator: 0 },
total_orders: { name: 'Orders', typefilter: 'number', filtercomparator: 0 },
recipient_clicks: { name: 'klikken', typefilter: 'number', filtercomparator: 0 },
total_revenue: { name: 'Totale opbrangest', typefilter: 'number', filtercomparator: 0, 'after': '€' },
};
*/
//'page_fans','page_fans_country','page_fans_by_like_source','page_fans_gender_age'
$scope.reportconfig = {
mail: {
views: {
campaigns: {
name: 'Campagnes',
view: 'campaigns',
second: { dimension: 'title', 'report': 'campaigns' },
selectedmetric: 'member_count',
dimension: 'title',
metrics: ['open_rate', 'click_rate', 'emails_sent', 'send_time', 'unsubscribed', 'bounces'],
table: { dataset: 'second' },
charts: {},
},
campaign: {
name: 'Campagne',
view: 'campaign',
hide: true,
first: { dimension: 'title', 'report': 'campaign.sent_to', filterexpression: $scope.urlfilter },
second: { dimension: 'title', 'report': 'campaign', filterexpression: $scope.urlfilter },
selectedmetric: 'Placeholder',
dimension: 'title',
metrics: ['open_rate', 'click_rate', 'emails_sent', 'unsubscribed', 'bounces', 'total_orders', 'total_revenue'],
panels: { dataset: 'stats' },
charts: {
Placeholder: [
{ 'chart': 'c0', 'name': 'Emails sent', type: 'line', dataset: 'timeseries', metric: 'emails_sent', dimension: 'date' },
{ 'chart': 'c1', 'name': 'Emails opened', type: 'line', dataset: 'timeseries', metric: 'unique_opens', dimension: 'date' },
{ 'chart': 'c2', 'name': 'Clicks', type: 'line', dataset: 'timeseries', metric: 'recipient_clicks', dimension: 'date' },
],
},
},
lists: {
name: 'Lijsten',
view: 'lists',
second: { dimension: 'name', 'report': 'lists' },
selectedmetric: 'member_count',
dimension: 'name',
metrics: ['member_count', 'click_rate', 'open_rate', 'list_rating'],
table: { dataset: 'second' },
charts: {},
},
list: {
name: 'List',
view: 'list',
hide: true,
first: { dimension: 'title', 'report': 'list', filterexpression: $scope.urlfilter },
second: { dimension: 'title', 'report': 'list.activity', filterexpression: $scope.urlfilter },
third: { dimension: 'title', 'report': 'list.members', filterexpression: $scope.urlfilter },
selectedmetric: 'Placeholder',
dimension: 'title',
metrics: ['open_rate', 'click_rate', 'member_count', 'unsubscribe_count', 'cleaned_count'],
panels: { dataset: 'first' },
charts: {
Placeholder: [
{ 'chart': 'c0', 'name': 'Emails sent', type: 'line', dataset: 'second', metric: 'emails_sent', dimension: 'date' },
{ 'chart': 'c1', 'name': 'Emails opened', type: 'line', dataset: 'second', metric: 'unique_opens', dimension: 'date' },
{ 'chart': 'c2', 'name': 'Clicks', type: 'line', dataset: 'second', metric: 'recipient_clicks', dimension: 'date' },
],
},
},
},
},
}
$controller('coreapi', { $scope: $scope });
$scope.init();
}
]);
app.controller('mybusiness', ['$scope', '$timeout', '$http', '$rootScope', '$controller', '$filter', 'h',
function($scope, $timeout, $http, $rootScope, $controller, $filter, h) {
$scope.cfg = {
authurl: 'https://app.marketingtracer.com/rest/mybusiness/connect?websiteid=' + h.siteId(),
dateformat: "YYYY-MM-DD",
dimensionprefix: '',
urlbase: 'mybusiness',
report_endpoint: '/rest/mybusiness/report',
}
$scope.gloMetrics = h.metrics('mybusiness');
/* $scope.gloMetrics = {
Placeholder: { name: '', typefilter: 'number', filtercomparator: 0 },
QUERIES_DIRECT: { name: 'Directe zoekopdrachten', typefilter: 'number', filtercomparator: 0 },
QUERIES_INDIRECT: { name: 'Indirecte zoekopdrachten', typefilter: 'number', filtercomparator: 0 },
VIEWS_MAPS: { name: 'Google maps', typefilter: 'number', filtercomparator: 0 },
VIEWS_SEARCH: { name: 'Google zoeken', typefilter: 'number', filtercomparator: 0 },
QUERIES_DIRECT: { name: 'Bedrijf zoekopdacht', typefilter: 'number', filtercomparator: 0 },
QUERIES_INDIRECT: { name: 'Categorie zoekopdracht', typefilter: 'number', filtercomparator: 0 },
PHOTOS_VIEWS_MERCHANT: { name: 'Van eigenaars', typefilter: 'number', filtercomparator: 0 },
PHOTOS_VIEWS_CUSTOMERS: { name: 'Van bezoekers', typefilter: 'number', filtercomparator: 0 },
PHOTOS_COUNT_MERCHANT: { name: 'Van eigenaar', typefilter: 'number', filtercomparator: 0 },
PHOTOS_COUNT_CUSTOMERS: { name: 'Van bezoekers', typefilter: 'number', filtercomparator: 0 },
Date: { name: 'Datum', typefilter: 'number', filtercomparator: 0 },
AverageCpc: { name: 'Gemiddelde kosten per klik', typefilter: 'number', filtercomparator: 2, 'before': '€' },
Ctr: { name: 'Ctr', typefilter: 'number', filtercomparator: 0, 'after': '%' },
Conversions: { name: 'Conversions', typefilter: 'number', filtercomparator: 0 },
Cost: { name: 'Cost', typefilter: 'number', filtercomparator: 0, 'before': '€' },
CostPerConversion: { name: 'Kosten per conversie', typefilter: 'number', filtercomparator: 0, 'before': '€' },
ConversionRate: { name: 'Conversie ratio', typefilter: 'number', filtercomparator: 0, 'after': '%' },
AveragePosition: { name: 'Gemiddelde positie', typefilter: 'number', filtercomparator: 1 },
ConversionValue: { name: 'Totale conversiewaarde', typefilter: 'number', filtercomparator: 0, 'before': '€' },
};
*/
$scope.fnFormatAd = function(d) {
return d.HeadlinePart1 + d.HeadlinePart2 + d.Description + d.CreativeDestinationUrl + d.CreativeFinalUrls;
}
$scope.reportconfig = {
business: {
views: {
all: {
name: 'Overzicht',
view: 'all',
first: { report: 'total' },
second: { report: 'daily' },
selectedmetric: 'Placeholder',
metrics: [],
table: { dataset: 'second' },
panels: { dataset: 'second' },
charts: {
Placeholder: [
{ 'chart': 'c1', 'name': 'Search type', type: 'bar', dataset: 'queries', 'dimension': 'type', 'metric': 'queries' },
{ 'chart': 'c2', 'name': 'Searches / day', type: 'pie', type: 'line', dataset: 'second', metric: 'QUERIES', dimension: 'date' },
{ 'chart': 'c3', 'name': 'Impressions / type', type: 'bar', dataset: 'views', 'dimension': 'type', 'metric': 'views' },
{ 'chart': 'c4', 'name': 'Impressions / day', type: 'pie', type: 'line', dataset: 'second', metric: 'VIEWS', dimension: 'date' },
{ 'chart': 'c5', 'name': 'Available Photo\'s', type: 'pie', dataset: 'photocount', 'dimension': 'type', 'metric': 'photocount' },
{ 'chart': 'c6', 'name': 'Photo Impressions', type: 'pie', dataset: 'photoview', 'dimension': 'type', 'metric': 'photoview' },
{ 'chart': 'c7', 'name': 'Website clicks', type: 'pie', type: 'line', dataset: 'second', metric: 'ACTIONS_WEBSITE', dimension: 'date' },
{ 'chart': 'c8', 'name': 'Phone contact', type: 'pie', type: 'line', dataset: 'second', metric: 'ACTIONS_PHONE', dimension: 'date' },
{ 'chart': 'c9', 'name': 'Driving Directions', type: 'pie', type: 'line', dataset: 'second', metric: 'ACTIONS_DRIVING_DIRECTIONS', dimension: 'date' },
],
},
},
reviews: {
name: 'Reviews',
view: 'reviews',
first: { report: 'reviews' },
selectedmetric: 'Placeholder',
metrics: [],
table: { dataset: 'second' },
panels: { dataset: 'second' },
charts: {
Placeholder: [
{ 'chart': 'c1', 'name': 'Search type', type: 'bar', dataset: 'queries', 'dimension': 'type', 'metric': 'queries' },
{ 'chart': 'c2', 'name': 'Zoekopdrachten / day', type: 'pie', type: 'line', dataset: 'second', metric: 'QUERIES', dimension: 'date' },
{ 'chart': 'c3', 'name': 'Impressions / type', type: 'bar', dataset: 'views', 'dimension': 'type', 'metric': 'views' },
{ 'chart': 'c4', 'name': 'Impressions / day', type: 'pie', type: 'line', dataset: 'second', metric: 'VIEWS', dimension: 'date' },
{ 'chart': 'c5', 'name': 'Photo\'s', type: 'pie', dataset: 'photocount', 'dimension': 'type', 'metric': 'photocount' },
{ 'chart': 'c6', 'name': 'Photo Impressions', type: 'pie', dataset: 'photoview', 'dimension': 'type', 'metric': 'photoview' },
{ 'chart': 'c7', 'name': 'Website clicks', type: 'pie', type: 'line', dataset: 'second', metric: 'ACTIONS_WEBSITE', dimension: 'date' },
{ 'chart': 'c8', 'name': 'Phone contact', type: 'pie', type: 'line', dataset: 'second', metric: 'ACTIONS_PHONE', dimension: 'date' },
{ 'chart': 'c9', 'name': 'Driving Directions', type: 'pie', type: 'line', dataset: 'second', metric: 'ACTIONS_DRIVING_DIRECTIONS', dimension: 'date' },
],
},
},
},
}
}
$controller('coreapi', { $scope: $scope });
$scope.init();
}
]);
/*
The current navigation is too literal. It names the data dimension, not the insight. We'll change it to be benefit-driven.
Current Text Proposed Change Why It's Better
Keywords Keyword Opportunities Focuses on the goal: finding new keyword targets.
Pages Top Content Highlights that these are the most important pages.
Country Geographic Performance Sounds more professional and comprehensive.
Device Device Analytics Aligns with industry-standard terminology.
Clusters Topic Intelligence Elevates the feature from a simple grouping to a strategic insight. This is the key change.
Gaps & Gains Gaps & Gains Clearly communicates finding new opportunities and plugging leaks.
*/
app.controller("hub", [
"$scope",
"$timeout",
"$http",
"$rootScope",
"$controller",
"$filter",
"h",
function ($scope, $timeout, $http, $rootScope, $controller, $filter, h) {
$scope.cfg = {
authurl: "https://app.marketingtracer.com/rest/searchconsole/connect?websiteid=" + h.siteId(),
dateformat: "YYYY-MM-DD",
dimensionprefix: "",
urlbase: "hub",
report_endpoint: "/rest/hub/report",
};
if ($rootScope.user.reseller?.config?.authdomain) {
$scope.cfg.authurl = "https://" + $rootScope.user.reseller.config.authdomain + "/rest/searchconsole/connect?websiteid=" + h.siteId();
}
$scope.search = {
updateFilter: function (query) {
$scope.search.query = query;
},
query: "",
};
$scope.gloMetrics = {
...h.metrics("searchconsole"),
...h.metrics("analytics4"),
};
$scope.limit = 100;
$scope.clusterProgress = 0;
$scope.topicMode = "smart";
$scope.changeMode = "percentage";
$scope.clusterValue = { key: "none", hasValue: false };
$scope.starredKeywords = new Set();
$scope.starringKeywords = new Set();
$scope.customTopic = {
displayName: "",
exact_keywords: ["", "", ""],
example_keywords: ["", "", ""],
boost_terms: ["", ""],
type: "exact",
};
$scope.customTopicSubmitting = false;
$scope.customTopicMessages = {};
$scope.setChangeMode = function (mode) {
$scope.changeMode = mode;
};
$scope.setTopicMode = function (mode) {
$scope.topicMode = mode;
var starredSet = new Set($scope.analytics.second.data.starred || []);
$scope.starredKeywords = starredSet;
if (mode === "keywords") {
$scope.currentKeywords = $scope.analytics.second.data.keywords || [];
} else if (mode === "starred") {
// Filter keywords to only show starred ones
var allKeywords = $scope.analytics.second.data.keywords || [];
$scope.currentKeywords = allKeywords.filter(function (keyword) {
return starredSet.has(keyword.query);
});
console.log("Starred keywords filtered:", $scope.currentKeywords.length, "out of", allKeywords.length);
}
};
$scope.resetCustomTopicForm = function (form) {
$scope.customTopic = {
displayName: "",
exact_keywords: ["", "", ""],
example_keywords: ["", "", ""],
boost_terms: ["", ""],
type: "default",
};
if (form) {
form.$setPristine();
form.$setUntouched();
}
};
$scope.submitCustomTopic = function (form) {
if (form && form.$invalid) {
form.$setSubmitted();
return;
}
var payload = {
display_name: ($scope.customTopic.displayName || "").trim(),
type: $scope.customTopic.type || "default",
example_keywords: $scope.customTopic.example_keywords
.map(function (keyword) {
return (keyword || "").trim();
})
.filter(function (keyword) {
return keyword.length > 0;
}),
exact_keywords: $scope.customTopic.exact_keywords
.map(function (term) {
return (term || "").trim();
})
.filter(function (term) {
return term.length > 0;
}),
boost_terms: $scope.customTopic.boost_terms
.map(function (term) {
return (term || "").trim();
})
.filter(function (term) {
return term.length > 0;
}),
};
// Validate based on type
if (!payload.display_name) {
$scope.customTopicMessages = {
error: "Provide a display name.",
};
if (form) {
form.$setSubmitted();
}
return;
}
if (payload.type === "exact" && payload.exact_keywords.length === 0) {
$scope.customTopicMessages = {
error: "Provide at least one exact keyword.",
};
if (form) {
form.$setSubmitted();
}
return;
}
if (payload.type !== "exact" && payload.example_keywords.length < 3) {
$scope.customTopicMessages = {
error: "Provide at least three example keywords.",
};
if (form) {
form.$setSubmitted();
}
return;
}
// If editing, add the cluster ID to the payload
if ($scope.customTopic.customClusterId) {
payload.custom_cluster_id = $scope.customTopic.customClusterId;
}
$scope.customTopicSubmitting = true;
$scope.customTopicMessages = {};
var endpoint = $scope.customTopic.customClusterId ? "/rest/searchconsole/cluster_custom_edit?websiteid=" + h.siteId() : "/rest/searchconsole/cluster_custom_add?websiteid=" + h.siteId();
$http
.post(endpoint, payload)
.then(function (response) {
$scope.customTopicMessages = {
success: $scope.customTopic.customClusterId ? "Custom topic edited successfully." : "Custom topic saved successfully.",
};
$scope.resetCustomTopicForm(form);
if (angular.isFunction($scope.getReportData)) {
$scope.getReportData();
}
})
.catch(function (error) {
var message = error && error.data && error.data.message ? error.data.message : "Unable to save custom topic at this time.";
$scope.customTopicMessages = {
error: message,
};
})
.finally(function () {
$scope.customTopicSubmitting = false;
});
};
$scope.fnEditCustomCluster = function (cluster) {
// FIRST: Set the type so the form shows the right section
$scope.customTopic.type = cluster.type || "default";
$scope.customTopic.displayName = cluster.cluster_name;
$scope.customTopic.customClusterId = cluster.custom_cluster_id;
// Load example keywords if they exist
if (cluster.example_keywords && Array.isArray(cluster.example_keywords)) {
var keywordStrings = cluster.example_keywords.map(function (kw) {
return typeof kw === "object" && kw.keyword ? kw.keyword : typeof kw === "string" ? kw : "";
});
while (keywordStrings.length < 3) {
keywordStrings.push("");
}
$scope.customTopic.example_keywords = keywordStrings;
}
// Load exact keywords if they exist
if (cluster.exact_keywords && Array.isArray(cluster.exact_keywords)) {
var exactKeywordStrings = cluster.exact_keywords.map(function (ek) {
return typeof ek === "object" && ek.term ? ek.term : typeof ek === "string" ? ek : "";
});
while (exactKeywordStrings.length < 3) {
exactKeywordStrings.push("");
}
$scope.customTopic.exact_keywords = exactKeywordStrings;
} else {
$scope.customTopic.exact_keywords = ["", "", ""];
}
// Load boost terms if they exist
if (cluster.boost_terms && Array.isArray(cluster.boost_terms)) {
var boostTermStrings = cluster.boost_terms.map(function (bt) {
return typeof bt === "object" && bt.term ? bt.term : typeof bt === "string" ? bt : "";
});
while (boostTermStrings.length < 2) {
boostTermStrings.push("");
}
$scope.customTopic.boost_terms = boostTermStrings;
} else {
$scope.customTopic.boost_terms = ["", ""];
}
// Scroll to form
document.querySelector('[name="customTopicForm"]').scrollIntoView({ behavior: "smooth" });
};
$scope.fnAddKeywordField = function () {
if (!$scope.customTopic.example_keywords) {
$scope.customTopic.example_keywords = ["", "", ""];
}
$scope.customTopic.example_keywords.push("");
};
$scope.fnAddBoostTermField = function () {
if (!$scope.customTopic.boost_terms) {
$scope.customTopic.boost_terms = ["", "", ""];
}
$scope.customTopic.boost_terms.push("");
};
$scope.fnRemoveBoostTerm = function (index) {
if ($scope.customTopic.boost_terms && index > 1 && index < $scope.customTopic.boost_terms.length) {
$scope.customTopic.boost_terms.splice(index, 1);
}
};
$scope.fnAddExactKeywordField = function () {
if (!$scope.customTopic.exact_keywords) {
$scope.customTopic.exact_keywords = ["", "", ""];
}
$scope.customTopic.exact_keywords.push("");
};
$scope.fnRemoveExactKeyword = function (index) {
if ($scope.customTopic.exact_keywords && index > 2 && index < $scope.customTopic.exact_keywords.length) {
$scope.customTopic.exact_keywords.splice(index, 1);
}
};
$scope.fnRemoveKeyword = function (index) {
if ($scope.customTopic.example_keywords && index > 2 && index < $scope.customTopic.example_keywords.length) {
$scope.customTopic.example_keywords.splice(index, 1);
}
};
$scope.range = function (start, end) {
var result = [];
for (var i = start; i < end; i++) {
result.push(i);
}
return result;
};
$scope.fnDeleteCustomCluster = function (cluster) {
if (!confirm("Are you sure you want to delete this custom topic cluster? This action cannot be undone.")) {
return;
}
$http
.post("/rest/searchconsole/cluster_custom_remove?websiteid=" + h.siteId(), {
custom_cluster_id: cluster.custom_cluster_id,
})
.then(function (response) {
console.log("Cluster deleted successfully");
if (angular.isFunction($scope.getReportData)) {
$scope.getReportData();
}
})
.catch(function (error) {
var message = error && error.data && error.data.message ? error.data.message : "Unable to delete custom topic at this time.";
alert("Error: " + message);
});
};
$scope.fnMoveKeywordToCluster = function (keyword, targetCluster) {
$http
.post("/rest/searchconsole/cluster_custom_move_keyword?websiteid=" + h.siteId(), {
keyword: keyword.query,
from_cluster_id: $scope.currentCluster.custom_cluster_id,
to_cluster_id: targetCluster.custom_cluster_id,
})
.then(function (response) {
console.log("Keyword moved successfully");
$scope.keywordMoveDropdown = null;
if (angular.isFunction($scope.getReportData)) {
$scope.getReportData();
}
})
.catch(function (error) {
var message = error && error.data && error.data.message ? error.data.message : "Unable to move keyword at this time.";
alert("Error: " + message);
});
};
$scope.fnToggleStarKeyword = function (keyword) {
var isStarred = $scope.starredKeywords.has(keyword.query);
var action = isStarred ? "unstar" : "star";
$scope.starringKeywords.add(keyword.query);
$http
.post("/rest/searchconsole/star?websiteid=" + h.siteId(), {
keyword: keyword.query,
action: action,
})
.then(function (response) {
$scope.starringKeywords.delete(keyword.query);
// ensure array exists
if (!$scope.analytics.second.data.starred) {
$scope.analytics.second.data.starred = [];
}
if (isStarred) {
$scope.starredKeywords.delete(keyword.query);
var index = $scope.analytics.second.data.starred.indexOf(keyword.query);
if (index > -1) {
$scope.analytics.second.data.starred.splice(index, 1);
}
} else {
$scope.analytics.second.data.starred.push(keyword.query);
$scope.starredKeywords.add(keyword.query);
}
})
.catch(function (error) {
$scope.starringKeywords.delete(keyword.query);
var message = error && error.data && error.data.message ? error.data.message : "Unable to star keyword at this time.";
alert("Error: " + message);
});
};
$scope.fnIsKeywordStarred = function (keyword) {
return $scope.starredKeywords.has(keyword.query);
};
$scope.starForm = { keyword: "" };
$scope.fnAddStarredKeywords = function () {
var raw = ($scope.starForm.keyword || "");
var lines = raw.split("\n");
var seen = {};
var keywords = [];
for (var i = 0; i < lines.length; i++) {
var kw = lines[i].trim().toLowerCase();
if (!kw || kw.length <= 2) continue;
if (seen[kw]) continue;
seen[kw] = true;
// Skip if already tracked
var exists = $scope.gscTrackerKeywords.some(function (t) { return t.keyword === kw; });
if (exists) continue;
keywords.push(kw);
}
if (keywords.length === 0) {
alert("No new keywords to add. Keywords must be longer than 2 characters and not already tracked.");
return;
}
$scope.importSource = "adding_keywords";
$scope.importing = true;
$scope.importProgress = 0;
$scope.importTotal = keywords.length;
var processKeyword = function (index) {
if (index >= keywords.length) {
$scope.importing = false;
$scope.starForm.keyword = "";
$scope.getReportData();
return;
}
$http.post("/rest/searchconsole/star?websiteid=" + h.siteId(), {
keyword: keywords[index],
action: "star"
}).then(function () {
$scope.importProgress++;
processKeyword(index + 1);
}, function () {
$scope.importProgress++;
processKeyword(index + 1);
});
};
processKeyword(0);
};
$scope.fnRemoveGscKeyword = function (kw, $event) {
if ($event) $event.stopPropagation();
if (!confirm('Remove "' + kw.keyword + '" from tracking?')) return;
$http
.post("/rest/searchconsole/star?websiteid=" + h.siteId(), {
keyword: kw.keyword,
action: "unstar",
})
.then(function () {
$scope.getReportData();
});
};
$scope.fnImportRankTracker = function () {
if (!confirm('Import keywords from legacy Rank Tracker? This may take a while.')) return;
$scope.importSource = "importing_ranktracker";
$scope.importing = true;
$scope.importProgress = 0;
$scope.importTotal = 0;
$http.get("/rest/searchconsole/import_ranktracker?websiteid=" + h.siteId())
.then(function (res) {
var keywords = res.data.keywords;
if (!keywords || keywords.length === 0) {
alert("No new keywords found to import.");
$scope.importing = false;
return;
}
$scope.importTotal = keywords.length;
var processKeyword = function(index) {
if(index >= keywords.length) {
$scope.importing = false;
$scope.getReportData(); // Refresh list
alert("Import completed!");
return;
}
var kw = keywords[index];
// Handle both string (old script) and object (new script) formats for robustness
var keyword = (typeof kw === 'object') ? kw.keyword : kw;
var group = (typeof kw === 'object') ? kw.group : '';
$http.post("/rest/searchconsole/star?websiteid=" + h.siteId(), {
keyword: keyword,
group: group,
action: "star"
}).then(function() {
$scope.importProgress++;
processKeyword(index + 1);
}, function() {
// Even if error, continue
$scope.importProgress++;
processKeyword(index + 1);
});
};
processKeyword(0);
});
};
$scope.fnToggleGscDetail = function (kw) {
$scope.gscDetailOpen[kw.keyword] = !$scope.gscDetailOpen[kw.keyword];
if ($scope.gscDetailOpen[kw.keyword] && kw.history && kw.history.length > 0 && !kw.historyChartId) {
$scope.fnBuildGscChart(kw);
}
};
$scope.fnSetGscHistoryMetric = function (metric, kw) {
$scope.gscHistoryMetric = metric;
if (kw && kw.history && kw.history.length > 0) {
$scope.fnBuildGscChart(kw);
}
};
$scope.fnBuildGscChart = function (kw) {
if (!kw || !kw.history || kw.history.length === 0) return;
var metric = $scope.gscHistoryMetric || "clicks";
// Merge daily and monthly data
var combinedData = [];
var dataMap = {};
if (kw.history) {
kw.history.forEach(function(item) {
var d = item.date;
if (!dataMap[d]) {
dataMap[d] = angular.copy(item);
combinedData.push(dataMap[d]);
}
});
}
if (kw.history_monthly) {
kw.history_monthly.forEach(function(item) {
var d = item.date; // Should be YYYY-MM-01
if (!dataMap[d]) {
dataMap[d] = { date: d };
combinedData.push(dataMap[d]);
}
// Add monthly metric with prefix
dataMap[d]['monthly_' + metric] = item[metric];
});
}
var sortedData = combinedData.sort(function (a, b) {
return new Date(a.date) - new Date(b.date);
});
var chartId = "gsc_" + kw.keyword.replace(/\s+/g, "_").toLowerCase();
$scope.pagecharts = $scope.pagecharts || {};
var graphs = [{
balloonText: "Daily " + metric + ": [[value]]",
bullet: "round", bulletBorderAlpha: 1, bulletColor: "#FFFFFF",
useLineColorForBulletBorder: true, fillAlphas: 0.2,
lineThickness: 2, lineAlpha: 0.5, bulletSize: 4,
connect: true, type: "smoothedLine",
title: "Daily " + metric, valueField: metric, lineColor: "#3F51B5"
}];
// Add monthly graph if data exists
if (kw.history_monthly && kw.history_monthly.length > 0) {
graphs.push({
balloonText: "Monthly " + metric + ": [[value]]",
bullet: "square", bulletBorderAlpha: 1, bulletColor: "#FFFFFF",
useLineColorForBulletBorder: true, fillAlphas: 0,
lineThickness: 3, lineAlpha: 1, bulletSize: 6,
connect: true, type: "smoothedLine",
title: "Monthly " + metric, valueField: 'monthly_' + metric, lineColor: "#FF9800"
});
}
$scope.pagecharts[chartId] = {
data: sortedData,
type: "serial",
theme: "light",
marginLeft: 0,
marginRight: 0,
marginTop: 10,
fontFamily: "Roboto",
categoryField: "date",
rotate: false,
pathToImages: "vendor/amchart/images/",
dataDateFormat: "YYYY-MM-DD",
categoryAxis: { gridPosition: "start", parseDates: true, gridThickness: 0 },
valueAxes: [{ position: "left", axisAlpha: 0, labelsEnabled: true, dashLength: 4, color: "#dedede" }],
graphs: graphs,
legend: { useGraphSettings: true, position: "top" }
};
kw.historyChartId = chartId;
if (!$scope.$$phase) {
$scope.$apply();
}
};
$scope.topLandingPages = [];
$scope.keywordDetailState = {};
$scope.checkAccessToken = function (data) {
if (data.action == "connect") {
$scope.modaloverride = true;
$scope.showConnectNudge = true;
}
$scope.cfg.urlbase = "searchconsole";
};
$scope.getClusterPendingState = function (data) {
if (data?.data?.status && data?.data?.status !== "completed") {
$scope.startClusterPolling();
}
// if($scope.clusterValueKey === "none") {
// $scope.hasRevenueContribution = false;
// }
// check if we have custom clusters
var hasCustomClusters = data?.data?.custom_cluster_metrics && data.data.custom_cluster_metrics.length > 0;
if (hasCustomClusters) {
$scope.topicMode = "custom";
}
__getRevenueInfo(data);
};
// --- GSC Tracker ---
$scope.gscTrackerKeywords = [];
$scope.gscDetailOpen = {};
$scope.gscHistoryMetric = "position";
$scope.gscSearch = { keyword: "" };
$scope.compareMode = 'mom';
$scope.fnSetCompareMode = function(mode) { $scope.compareMode = mode; };
// Country filter
$scope.gscCountry = null;
$scope.gscCountryOptions = [
{ code: 'nld', name: 'Netherlands' },
{ code: 'bel', name: 'Belgium' },
{ code: 'deu', name: 'Germany' },
{ code: 'gbr', name: 'United Kingdom' },
{ code: 'usa', name: 'United States' },
{ code: 'fra', name: 'France' },
{ code: 'esp', name: 'Spain' },
{ code: 'ita', name: 'Italy' },
{ code: 'aut', name: 'Austria' },
{ code: 'che', name: 'Switzerland' },
{ code: 'pol', name: 'Poland' },
{ code: 'swe', name: 'Sweden' },
{ code: 'nor', name: 'Norway' },
{ code: 'dnk', name: 'Denmark' },
{ code: 'fin', name: 'Finland' },
{ code: 'prt', name: 'Portugal' },
{ code: 'irl', name: 'Ireland' },
{ code: 'can', name: 'Canada' },
{ code: 'aus', name: 'Australia' },
{ code: 'bra', name: 'Brazil' },
{ code: 'ind', name: 'India' },
{ code: 'jpn', name: 'Japan' },
];
$scope.fnSetGscCountry = function(country) {
$scope.gscCountry = country;
$scope.fnRefreshGscTracker();
};
$scope.fnRefreshGscTracker = function() {
if ($scope.gscCountry) {
// Country-filtered: call dedicated endpoint with live GSC API
$http.get('/rest/searchconsole/gsctracker_country', {
params: {
websiteid: h.siteId(),
country: $scope.gscCountry.code,
start: $rootScope.reportdates.start,
end: $rootScope.reportdates.end
}
}).then(function(res) {
$scope.getStarredKeywords(res.data);
});
} else {
// No country: use standard coreapi fetch (reads from stored history)
$scope.getReportData();
}
};
// Callback for core API when data loads
$scope.getStarredKeywords = function (data) {
$scope.checkAccessToken(data);
$scope.gscTrackerKeywords = (data && data.data) ? data.data : [];
$scope.fnGroupKeywords();
// Visibility chart
if (data && data.visibility_history && data.visibility_history.length) {
$scope.fnBuildVisibilityChart(data.visibility_history);
}
};
$scope.fnBuildVisibilityChart = function(history) {
var sorted = history.slice().sort(function(a, b) { return a.date.localeCompare(b.date); });
$scope.visibilityChart = {
type: 'serial',
data: sorted,
categoryField: 'date',
dataDateFormat: 'YYYY-MM-DD',
categoryAxis: { parseDates: true, minPeriod: 'DD' },
graphs: [{
valueField: 'score',
type: 'smoothedLine',
lineColor: '#3b82f6',
fillAlphas: 0.1,
lineThickness: 2,
balloonText: '[[category]]: [[value]]'
}],
valueAxes: [{ minimum: 0, maximum: 100, title: 'Visibility Score' }],
chartCursor: { cursorAlpha: 0.1 }
};
};
$scope.fnGroupKeywords = function() {
if(!$scope.gscTrackerKeywords) return;
var groups = {};
// Initialize 'Ungrouped'
groups[''] = { name: 'Ungrouped', keywords: [] };
$scope.gscTrackerKeywords.forEach(function(kw) {
var groupName = kw.group || '';
var groupKey = groupName.toLowerCase();
if(!groups[groupKey]) {
groups[groupKey] = { name: groupName, keywords: [] };
}
groups[groupKey].keywords.push(kw);
});
// Convert to array for sorting, put Ungrouped last or first?
// Let's sort alphabetically, with Ungrouped at the bottom if empty?
// Actually, let's just convert to array.
$scope.gscTrackerGroups = [];
Object.keys(groups).forEach(function(key) {
if(groups[key].keywords.length > 0) {
$scope.gscTrackerGroups.push(groups[key]);
}
});
// Sort: named groups first, then ungrouped? Or just alpha.
$scope.gscTrackerGroups.sort(function(a, b) {
if(a.name === 'Ungrouped') return 1;
if(b.name === 'Ungrouped') return -1;
return a.name.localeCompare(b.name);
});
};
$scope.fnUpdateKeywordGroup = function (keyword) {
if (!keyword || !keyword.keyword) return;
$http.post("/rest/searchconsole/gsctracker_update?websiteid=" + h.siteId(), {
keyword: keyword.keyword,
group: keyword.group
}).then(function() {
keyword.isEditing = false;
// Re-group after update
$scope.fnGroupKeywords();
});
};
$scope.getFilteredCluster = function (data) {
// Load starred keywords from the data
$scope.starredKeywords = new Set();
if (data && data.data && Array.isArray(data.data.starred)) {
data.data.starred.forEach(function (keyword) {
$scope.starredKeywords.add(keyword);
});
}
if ($scope.urlfilter && data?.data?.cluster_metrics) {
// ic cluster starts with custom
if ($scope.urlfilter.startsWith("custom_")) {
$scope.currentCluster = data.data.custom_cluster_metrics.filter((c) => c.custom_cluster_id == $scope.urlfilter)?.[0];
$scope.currentKeywords = data.data.keywords?.filter((k) => k.custom_cluster_id == $scope.urlfilter) || [];
} else {
$scope.currentCluster = data.data.cluster_metrics.filter((c) => c.cluster_id == $scope.urlfilter)?.[0];
$scope.currentKeywords = data.data.keywords?.filter((k) => k.cluster_id == $scope.urlfilter) || [];
}
$scope.topLandingPages = $scope.buildTopLandingPages($scope.currentKeywords);
$scope.keywordDetailState = {};
}
__getRevenueInfo(data);
// Hide revenue contribution on cluster detail view
$scope.selectedValueFields = { value: null, percent: null };
};
var __getRevenueInfo = function (data) {
// check if we have a Revenue Contribution anywhere?
var hasRevenueContribution = false;
if (data?.data?.cluster_metrics) {
//hasRevenueContribution = data.data.cluster_metrics.some((metric) => metric.estimated_value_percent !== undefined && metric.estimated_value_percent > 0);
}
$scope.clusterValue.key = data?.data?.clusterValueKey ? data.data.clusterValueKey : "none";
// Map the selected cluster value key to the appropriate metric fields
var valueFieldMap = {
"ecommerce-value": { value: "estimated_ecommerce_value", percent: "estimated_ecommerce_value_percent" },
"goal-value": { value: "estimated_goal_value", percent: "estimated_goal_value_percent" },
"goal-number": { value: "estimated_goal_number", percent: "estimated_goal_number_percent" },
none: { value: null, percent: null },
};
$scope.selectedValueFields = valueFieldMap[$scope.clusterValue.key] || valueFieldMap["none"];
// re-check hasRevenueContribution based on selected cluster value key
// hasRevenueContribution = data.data.cluster_metrics.some((metric) => metric[$scope.selectedValueFields.value] !== undefined && metric[$scope.selectedValueFields.value] > 0);
$scope.clusterValue.hasValue = hasRevenueContribution;
/* OVERRIDE FOR NOW */
$scope.clusterValue.hasValue = false;
if (data.data?.custom_cluster_metrics_needsrefresh == true) {
$scope.clusterNeedsRefresh = true;
} else {
$scope.clusterNeedsRefresh = false;
}
};
$scope.buildTopLandingPages = function (keywords) {
if (!angular.isArray(keywords) || !keywords.length) {
return [];
}
var aggregated = {};
angular.forEach(keywords, function (keyword) {
if (!keyword || !angular.isArray(keyword.urls)) {
return;
}
angular.forEach(keyword.urls, function (urlInfo) {
if (!urlInfo || !urlInfo.url) {
return;
}
var key = urlInfo.url;
if (!aggregated[key]) {
aggregated[key] = {
url: urlInfo.url,
clicks: 0,
estimatedValue: 0,
keywords: [],
};
}
aggregated[key].clicks += urlInfo.clicks || 0;
aggregated[key].estimatedValue += urlInfo.estimatedValue || 0;
if (keyword.query && aggregated[key].keywords.indexOf(keyword.query) === -1) {
aggregated[key].keywords.push(keyword.query);
}
});
});
var rows = [];
angular.forEach(aggregated, function (item) {
rows.push({
url: item.url,
clicks: item.clicks,
estimatedValue: item.estimatedValue,
keywordCount: item.keywords.length,
topKeyword: item.keywords[0] || "",
});
});
rows.sort(function (a, b) {
return (b.estimatedValue || 0) - (a.estimatedValue || 0);
});
return rows.slice(0, 20);
};
$scope.fnExportCSV = function () {
var data = [];
var filename = "export.csv";
var headers = [];
if ($scope.topicMode === "smart") {
data = $scope.analytics.second.data.cluster_metrics;
headers = ["Cluster Name", "Keyword Count", "Position", "Position Change", "Position Change %", "Clicks", "Clicks Change", "Clicks Change %", "Impressions", "CTR"];
filename = "smart_clusters.csv";
} else if ($scope.topicMode === "custom") {
data = $scope.analytics.second.data.custom_cluster_metrics;
headers = ["Cluster Name", "Keyword Count", "Position", "Position Change", "Position Change %", "Clicks", "Clicks Change", "Clicks Change %", "Impressions", "CTR"];
filename = "custom_clusters.csv";
} else if ($scope.topicMode === "keywords" || $scope.topicMode === "starred") {
data = $scope.currentKeywords;
headers = ["Keyword", "Position", "Position Change", "Position Change %", "Clicks", "Clicks Change", "Clicks Change %", "Impressions", "CTR", "URL"];
filename = ($scope.topicMode === "starred" ? "starred_" : "") + "keywords.csv";
}
if (!data || data.length === 0) {
alert("No data to export");
return;
}
var csvContent = headers.join(",") + "\n";
data.forEach(function (row) {
var line = [];
// Calculate Changes
var posCurrent = row.metrics_current?.position || 0;
var posPrev = row.metrics_previous?.position || 0;
var posChange = posPrev > 0 ? posCurrent - posPrev : 0;
var posChangePct = posPrev > 0 ? (posCurrent - posPrev) / posPrev * 100 : 0;
var clicksCurrent = row.metrics_current?.clicks || 0;
var clicksPrev = row.metrics_previous?.clicks || 0;
var clicksChange = clicksCurrent - clicksPrev;
var clicksChangePct = clicksPrev > 0 ? (clicksCurrent - clicksPrev) / clicksPrev * 100 : 0;
if ($scope.topicMode === "smart" || $scope.topicMode === "custom") {
line.push('"' + (row.cluster_name || "").replace(/"/g, '""') + '"');
line.push(row.keyword_count || 0);
line.push(posCurrent.toFixed(1));
line.push(posChange.toFixed(1));
line.push(posChangePct.toFixed(1) + "%");
line.push(clicksCurrent);
line.push(clicksChange);
line.push(clicksChangePct.toFixed(1) + "%");
line.push(row.metrics_current?.impressions || 0);
line.push((row.metrics_current?.ctr * 100).toFixed(2) + "%");
} else {
line.push('"' + (row.query || "").replace(/"/g, '""') + '"');
line.push(posCurrent.toFixed(1));
line.push(posChange.toFixed(1));
line.push(posChangePct.toFixed(1) + "%");
line.push(clicksCurrent);
line.push(clicksChange);
line.push(clicksChangePct.toFixed(1) + "%");
line.push(row.metrics_current?.impressions || 0);
line.push((row.metrics_current?.ctr * 100).toFixed(2) + "%");
var urls = row.urls ? row.urls.map(u => u.url).join(";") : "";
line.push('"' + urls.replace(/"/g, '""') + '"');
}
csvContent += line.join(",") + "\n";
});
var blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
var link = document.createElement("a");
if (link.download !== undefined) {
var url = URL.createObjectURL(blob);
link.setAttribute("href", url);
link.setAttribute("download", filename);
link.style.visibility = "hidden";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
};
$scope.fnToggleKeywordDetails = function (keyword, $event) {
if ($event) {
$event.stopPropagation();
}
if (!keyword) {
return;
}
var key = keyword.query || keyword._id || keyword.keyword_id;
if (!key) {
return;
}
$scope.keywordDetailState[key] = !$scope.keywordDetailState[key];
// If opening details and history not already loaded, fetch it
if ($scope.keywordDetailState[key] && (!keyword.history || keyword.history.length === 0)) {
var reportUrl =
"/rest/hub/report?context=searchconsole&dimension=date&end=TODAY&metrics=position,clicks,ctr,impressions&report=searchAnalytics&start=1YEARAGO&websiteid=" +
h.siteId() +
"&multi_dimension=page_date&query=" +
encodeURIComponent(keyword.query) +
"&filterdimension=query&filterexpression=" +
encodeURIComponent(keyword.query) +
"&filteroperator=equals";
$http
.get(reportUrl)
.then(function (response) {
if (response.data && response.data.data && Array.isArray(response.data.data)) {
// If the data includes URL breakdowns, process it
var rawData = response.data.data;
// Check if data includes page/URL information
var hasUrlData = rawData.length > 0 && rawData[0].page;
if (hasUrlData) {
// Process data to aggregate by date and page/URL
var dataByDateAndUrl = {};
rawData.forEach(function (item) {
var dateKey = item.date;
if (!dataByDateAndUrl[dateKey]) {
dataByDateAndUrl[dateKey] = {
date: item.date,
};
}
// Store metrics by page/URL
var urlKey = "page_" + item.page;
if (!dataByDateAndUrl[dateKey][urlKey]) {
dataByDateAndUrl[dateKey][urlKey] = {};
}
dataByDateAndUrl[dateKey][urlKey] = {
clicks: item.clicks || 0,
impressions: item.impressions || 0,
ctr: item.ctr || 0,
position: item.position || 0,
};
});
// Convert back to array and add URL-specific metric fields
keyword.history = Object.keys(dataByDateAndUrl).map(function (dateKey) {
var dateData = dataByDateAndUrl[dateKey];
var aggregated = {
date: dateData.date,
clicks: 0,
impressions: 0,
ctr: 0,
position: 0,
urls: {},
};
// Aggregate and store URL-specific data
var urlIndex = 0;
Object.keys(dateData).forEach(function (key) {
if (key !== "date") {
var url = key.replace("page_", "");
aggregated.urls[url] = dateData[key];
// Also create flat fields for chart: url_0_clicks, url_1_clicks, etc.
var urlNum = urlIndex;
aggregated["url_" + urlNum + "_clicks"] = dateData[key].clicks;
aggregated["url_" + urlNum + "_impressions"] = dateData[key].impressions;
aggregated["url_" + urlNum + "_ctr"] = dateData[key].ctr;
aggregated["url_" + urlNum + "_position"] = dateData[key].position;
urlIndex++;
}
});
// Aggregate totals
Object.keys(dateData).forEach(function (key) {
if (key !== "date") {
aggregated.clicks += dateData[key].clicks || 0;
aggregated.impressions += dateData[key].impressions || 0;
aggregated.ctr = Math.max(aggregated.ctr, dateData[key].ctr || 0);
aggregated.position = Math.min(aggregated.position || 999, dateData[key].position || 999);
}
});
return aggregated;
});
} else {
// No URL data from API - use aggregated data but create per-URL fields
// This allows displaying separate lines for each URL tracking the same keyword performance
// Get unique URLs from keyword
var urls = [];
if (keyword.urls && Array.isArray(keyword.urls)) {
urls = keyword.urls.map(function (urlEntry) {
return urlEntry.url || urlEntry;
});
}
// Add URL-specific fields to each data point
keyword.history = rawData.map(function (item) {
var processed = {
date: item.date,
clicks: item.clicks || 0,
impressions: item.impressions || 0,
ctr: item.ctr || 0,
position: item.position || 0,
};
// Create per-URL metric fields - distribute data across URLs proportionally or equally
urls.forEach(function (url, index) {
// For now, assign the same metrics to each URL as a baseline
// You could modify this to distribute clicks proportionally if you have URL-level info
processed["url_" + index + "_clicks"] = item.clicks || 0;
processed["url_" + index + "_impressions"] = item.impressions || 0;
processed["url_" + index + "_ctr"] = item.ctr || 0;
processed["url_" + index + "_position"] = item.position || 0;
});
return processed;
});
}
// Build chart for the currently selected metric
$scope.buildKeywordHistoryChart(keyword);
} else {
}
})
.catch(function (error) {
keyword.history = [];
});
} else if ($scope.keywordDetailState[key] && keyword.history && keyword.history.length > 0) {
// If history is already loaded and details are being opened, rebuild chart (in case metric changed)
$scope.buildKeywordHistoryChart(keyword);
}
};
$scope.keywordHistoryMetric = "clicks"; // Default metric
$scope.fnSetKeywordHistoryMetric = function (metric) {
if (metric && metric !== $scope.keywordHistoryMetric) {
$scope.keywordHistoryMetric = metric;
// Rebuild all open keyword charts with the new metric
if ($scope.analytics && $scope.analytics.second && $scope.analytics.second.data && Array.isArray($scope.analytics.second.data.keywords)) {
$scope.analytics.second.data.keywords.forEach(function (keyword) {
if (keyword.history && keyword.history.length > 0 && $scope.fnKeywordIsOpen(keyword)) {
$scope.buildKeywordHistoryChart(keyword);
}
});
}
}
};
$scope.fnKeywordIsOpen = function (keyword) {
if (!keyword) {
return false;
}
var key = keyword.query || keyword._id || keyword.keyword_id;
if (!key) {
return false;
}
return !!$scope.keywordDetailState[key];
};
$scope.buildKeywordHistoryChart = function (keyword) {
if (!keyword || !keyword.history || !Array.isArray(keyword.history) || keyword.history.length === 0) {
return;
}
// Get the currently selected keyword history metric, default to 'clicks'
var metric = $scope.keywordHistoryMetric || "clicks";
// Sort data by date
var sortedData = keyword.history.slice().sort(function (a, b) {
return new Date(a.date) - new Date(b.date);
});
// Create unique chart ID based on keyword
var chartId = "keyword_history_" + (keyword.query || keyword._id || "").replace(/\s+/g, "_").toLowerCase();
// Create analytics structure for the chart function to use
$scope.analytics = $scope.analytics || {};
$scope.analytics.keywordHistory = {
data: sortedData,
};
// Initialize pagecharts if needed
$scope.pagecharts = $scope.pagecharts || {};
// Format the date strings properly for AmCharts
var datesubstr = $scope.cfg.dateformat == "YYYYMMDD" ? [0, 4, 4, 6, 6, 8] : [0, 4, 5, 7, 8, 10];
// Get unique URLs from the keyword
var urls = [];
if (keyword.urls && Array.isArray(keyword.urls)) {
urls = keyword.urls.map(function (urlEntry) {
return urlEntry.url || urlEntry;
});
}
// Color palette for different lines
var colorPalette = [
"#3F51B5", // Indigo
"#FF6B35", // Orange-Red
"#4ECDC4", // Teal
"#F7B731", // Yellow-Orange
"#5F27CD", // Purple
"#00D2D3", // Cyan
"#FF6348", // Coral
"#95E1D3", // Mint
"#38A3A5", // Dark Teal
"#C73E1D", // Dark Orange
];
// Build graphs array - one for each URL
var graphs = [];
if (urls.length > 0) {
// Multi-URL mode: create a graph for each URL
urls.forEach(function (url, index) {
var fieldName = "url_" + index + "_" + metric;
graphs.push({
balloonText: url + "
" + (metric.charAt(0).toUpperCase() + metric.slice(1)) + ": [[value]]",
bullet: "round",
bulletBorderAlpha: 1,
bulletColor: "#FFFFFF",
useLineColorForBulletBorder: true,
fillAlphas: 0.1,
lineThickness: 2,
lineAlpha: 1,
bulletSize: 5,
connect: false,
type: "smoothedLine",
title: url,
valueField: fieldName,
lineColor: colorPalette[index % colorPalette.length],
});
});
} else {
// Single line mode (fallback if no URLs)
graphs.push({
balloonText: metric.charAt(0).toUpperCase() + metric.slice(1) + "
[[value]]",
bullet: "round",
bulletBorderAlpha: 1,
bulletColor: "#FFFFFF",
useLineColorForBulletBorder: true,
fillAlphas: 0.2,
lineThickness: 2,
lineAlpha: 1,
bulletSize: 7,
connect: true,
type: "smoothedLine",
title: metric.charAt(0).toUpperCase() + metric.slice(1),
valueField: metric,
lineColor: "#3F51B5",
});
}
// Build the AmCharts configuration matching the exact structure from fnLineChart
$scope.pagecharts[chartId] = {
data: sortedData,
type: "serial",
theme: "light",
marginLeft: 0,
marginRight: 0,
marginTop: 10,
fontFamily: "Roboto",
categoryField: "date",
rotate: false,
pathToImages: "vendor/amchart/images/",
dataDateFormat: $scope.cfg.dateformat,
categoryAxis: {
gridPosition: "start",
parseDates: true,
gridThickness: 0,
},
valueAxes: [
{
position: "left",
axisAlpha: 0,
labelsEnabled: true,
dashLength: 4,
color: "#dedede",
},
],
graphs: graphs,
legend:
urls.length > 1
? {
enabled: true,
position: "top",
marginBottom: 10,
}
: undefined,
};
// Store chart reference on keyword for easy access
keyword.historyChartId = chartId;
// Trigger Angular digest to update the view
if (!$scope.$$phase) {
$scope.$apply();
}
};
$scope.setAnalyticsConfig = function (data) {
$scope.cfg.dateformat = "YYYYMMDD";
};
$scope.reportconfig = {
v1: {
views: {
searchperformance: {
name: "New, Lost and Opprotunities",
view: "searchperformance",
fullchart: true,
/*timeline fallback */
first: { dimension: "date", context: "searchconsole", report: "searchAnalytics", start: "1YEARAGO", callback: $scope.checkAccessToken, compare: "PERIOD" },
/* clusters */
second: { dimension: "query", context: "searchconsole", report: "clusters", start: "60DAYSAGO", end: "TODAY", limit: $scope.limit, callback: $scope.getClusterPendingState },
/* panels */
third: { dimension: "date", context: "searchconsole", report: "searchAnalytics", compare: "PERIOD" },
/* branded timeline */
fourth: { dimension: "date", context: "searchconsole", report: "clusters_branded_timeline", dimension2: "label" },
selectedmetric: "clicks",
metrics: ["position", "clicks", "ctr", "impressions"],
hide: false,
panels: { dataset: "third" },
selectedmetrics: { clicks: true, ctr: true, position: true, impressions: true },
charts: {
clicks: [
{ chart: "c1", name: "Clicks", type: "line", dataset: "first", date: true, stacked: true, active: true },
{ chart: "c2", name: "Branded vs Non Branded Clicks", type: "linemulti", dataset: "fourth", date: true, stacked: true, graphs: ["branded", "non_branded"] },
{ chart: "c3", name: "Click Intentions", type: "linemulti", dataset: "fourth", date: true, stacked: true, graphs: ["informational", "navigational", "transactional", "commercial"] },
],
ctr: [
{ chart: "c1", name: "Ctr", type: "line", dataset: "first", date: true, active: true },
{ chart: "c2", name: "Branded vs Non Branded Ctr", type: "linemulti", dataset: "fourth", date: true, graphs: ["branded", "non_branded"] },
{ chart: "c3", name: "Ctr Intentions", type: "linemulti", dataset: "fourth", date: true, graphs: ["informational", "navigational", "transactional", "commercial"] },
],
position: [
{ chart: "c1", name: "Position", type: "line", dataset: "first", date: true, active: true },
{ chart: "c2", name: "Branded vs Non Branded Position", type: "linemulti", dataset: "fourth", date: true, graphs: ["branded", "non_branded"] },
{ chart: "c3", name: "Position Intentions", type: "linemulti", dataset: "fourth", date: true, graphs: ["informational", "navigational", "transactional", "commercial"] },
],
impressions: [
{ chart: "c1", name: "Impressions", type: "line", dataset: "first", date: true, active: true },
{ chart: "c2", name: "Branded vs Non Branded Impressions", type: "linemulti", dataset: "fourth", date: true, graphs: ["branded", "non_branded"] },
{ chart: "c3", name: "Impressions Intentions", type: "linemulti", dataset: "fourth", date: true, graphs: ["informational", "navigational", "transactional", "commercial"] },
],
},
},
keywordperformance: {
name: "Starred Keywords",
view: "keywordperformance",
hide: false,
second: { dimension: "query", context: "searchconsole", report: "gsctracker_list", callback: $scope.getStarredKeywords },
selectedmetric: "clicks",
selectedmetrics: { clicks: true, ctr: true, position: true, impressions: true },
metrics: ["position", "clicks", "ctr", "impressions"],
},
clusterperformance: {
name: $scope.urlfilter,
hide: true,
view: "clusterperformance",
first: { dimension: "date", context: "searchconsole", report: "searchAnalytics" },
second: { dimension: "query", context: "searchconsole", report: "clusters", start: "60DAYSAGO", end: "TODAY", limit: $scope.limit, callback: $scope.getFilteredCluster },
selectedmetric: "clicks",
selectedmetrics: { clicks: true, ctr: true, position: true, impressions: true },
metrics: ["position", "clicks", "ctr", "impressions"],
// charts: {
// clicks: [{ 'chart': 'c1', 'name': 'Clicks', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Clicks per keyword', type: 'progress', dataset: 'third' }],
// ctr: [{ 'chart': 'c1', 'name': 'Ctr', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Ctr per keyword', type: 'progress', dataset: 'third' }],
// position: [{ 'chart': 'c1', 'name': 'Position', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Position per keyword', type: 'progress', dataset: 'third' }],
// impressions: [{ 'chart': 'c1', 'name': 'Impressions', type: 'line', dataset: 'first', date: true }, { 'chart': 'c2', 'name': 'Impressions per keyword', type: 'progress', dataset: 'third' }],
// },
},
searchengines: {
name: "searchengines",
view: "searchengines",
hide: true,
viewfilters: ["goals"],
first: { context: "analytics", dimension: "date", dimension2: "sessionSource", channel: "Organic Search", callback: $scope.setAnalyticsConfig },
second: { context: "analytics", dimension: "sessionSource", sort: "sessions", sortorder: "DESCENDING", allowcomp: true, channel: "Organic Search", callback: $scope.setAnalyticsConfig },
selectedmetric: "sessions",
selectedmetrics: { sessions: true, totalUsers: true, screenPageViews: true, screenPageViewsPerSession: true, averageSessionDuration: true, newUsers: true, bounceRate: true, bounceRate: true, eventCount: true, eventCountPerUser: true },
metrics: ["sessions", "totalUsers", "screenPageViews", "screenPageViewsPerSession", "averageSessionDuration", "newUsers", "bounceRate", "eventCount", "eventCountPerUser"],
table: { dataset: "second" },
panels: { dataset: "second" },
charts: {
sessions: [
{ chart: "c1", name: "Sessions", type: "linemulti", dataset: "first", date: true },
{ chart: "c2", name: "Sessions / channel", type: "pie", dataset: "second" },
],
totalUsers: [
{ chart: "c1", name: "Users", type: "linemulti", dataset: "first", date: true },
{ chart: "c2", name: "Users / channel", type: "pie", dataset: "second" },
],
screenPageViews: [
{ chart: "c1", name: "Pagina weergaven", type: "linemulti", dataset: "first", date: true },
{ chart: "c2", name: "screenPageViews / channel", type: "pie", dataset: "second" },
],
screenPageViewsPerSession: [
{ chart: "c1", name: "Pageviews per session", type: "linemulti", dataset: "first", date: true },
{ chart: "c2", name: "screenPageViewsPerSession per session /channel", type: "bar", dataset: "second" },
],
averageSessionDuration: [
{ chart: "c1", name: "Average session duration", type: "linemulti", dataset: "first", date: true },
{ chart: "c2", name: "_", type: "bar", dataset: "second" },
],
newUsers: [
{ chart: "c1", name: "Percentage new Sessions", type: "linemulti", dataset: "first", date: true },
{ chart: "c2", name: "_", type: "bar", dataset: "second" },
],
bounceRate: [
{ chart: "c1", name: "Bounce ratio", type: "linemulti", dataset: "first", date: true },
{ chart: "c2", name: "_", type: "bar", dataset: "second" },
],
eventCount: [
{ chart: "c1", name: "Goals", type: "linemulti", dataset: "first", date: true },
{ chart: "c2", name: "_", type: "pie", dataset: "second" },
],
eventCountPerUser: [
{ chart: "c1", name: "Goalvalue", type: "linemulti", dataset: "first", date: true },
{ chart: "c2", name: "_", type: "pie", dataset: "second" },
],
},
},
llmtraffic: {
name: "llmtraffic",
view: "llmtraffic",
hide: false,
viewfilters: ["goals"],
first: { context: "analytics", dimension: "date", dimension2: "sessionSource", channel: "custom_llm", start: "6MONTHSAGO", end: "TODAY", callback: $scope.setAnalyticsConfig },
second: { context: "analytics", dimension: "sessionSource", sort: "sessions", sortorder: "DESCENDING", allowcomp: true, channel: "custom_llm", start: "6MONTHSAGO", end: "TODAY", callback: $scope.setAnalyticsConfig },
selectedmetric: "sessions",
selectedmetrics: { sessions: true, totalUsers: true, screenPageViews: true, screenPageViewsPerSession: true, averageSessionDuration: true, newUsers: true, bounceRate: true, eventCount: true },
metrics: ["sessions", "totalUsers", "screenPageViews", "screenPageViewsPerSession", "averageSessionDuration", "newUsers", "bounceRate", "eventCount"],
table: { dataset: "second" },
panels: { dataset: "second" },
charts: {
sessions: [
{ chart: "c1", name: "Sessions", type: "linemulti", stacked: true, dataset: "first", date: true },
{ chart: "c2", name: "Sessions / channel", type: "pie", dataset: "second" },
],
totalUsers: [
{ chart: "c1", name: "Users", type: "linemulti", stacked: true, dataset: "first", date: true },
{ chart: "c2", name: "Users / channel", type: "pie", dataset: "second" },
],
screenPageViews: [
{ chart: "c1", name: "Pagina weergaven", type: "linemulti", stacked: true, dataset: "first", date: true },
{ chart: "c2", name: "screenPageViews / channel", type: "pie", dataset: "second" },
],
screenPageViewsPerSession: [
{ chart: "c1", name: "Pageviews per session", type: "linemulti", stacked: true, dataset: "first", date: true },
{ chart: "c2", name: "screenPageViewsPerSession per session /channel", type: "bar", dataset: "second" },
],
averageSessionDuration: [
{ chart: "c1", name: "Average session duration", type: "linemulti", stacked: true, dataset: "first", date: true },
{ chart: "c2", name: "_", type: "bar", dataset: "second" },
],
newUsers: [
{ chart: "c1", name: "Percentage new Sessions", type: "linemulti", stacked: true, dataset: "first", date: true },
{ chart: "c2", name: "_", type: "bar", dataset: "second" },
],
bounceRate: [
{ chart: "c1", name: "Bounce ratio", type: "linemulti", stacked: true, dataset: "first", date: true },
{ chart: "c2", name: "_", type: "bar", dataset: "second" },
],
eventCount: [
{ chart: "c1", name: "Goals", type: "linemulti", stacked: true, dataset: "first", date: true },
{ chart: "c2", name: "_", type: "pie", dataset: "second" },
],
},
},
aioverview: {
name: "aioverview",
view: "aioverview",
hide: false,
viewfilters: ["goals"],
first: { context: "searchconsole", report: "aioverview", dimension: "date", start: "6MONTHSAGO", end: "TODAY" },
second: { context: "searchconsole", report: "aioverview", dimension: "query", start: "6MONTHSAGO", end: "TODAY" },
selectedmetric: "estimated_clicks",
selectedmetrics: { estimated_clicks: true },
metrics: ["estimated_clicks"],
charts: {
estimated_clicks: [{ chart: "c1", name: "Search Visibility", type: "line", dataset: "first", date: true, hideAxisLabels: true }],
},
},
info_and_roadmap: {
name: "info_and_roadmap",
view: "info_and_roadmap",
hide: false,
viewfilters: ["goals"],
selectedmetrics: { sessions: true, totalUsers: true, screenPageViews: true, screenPageViewsPerSession: true, averageSessionDuration: true, newUsers: true, bounceRate: true, bounceRate: true, eventCount: true, eventCountPerUser: true },
metrics: ["sessions", "totalUsers", "screenPageViews", "screenPageViewsPerSession", "averageSessionDuration", "newUsers", "bounceRate", "eventCount", "eventCountPerUser"],
},
},
},
};
$scope.fnSetLimit = function (limit) {
$scope.view.first.limit = limit;
$scope.getReportData();
};
/* CLustering */
$scope.fnStartClustering = function () {
$scope.analytics.second = { action: "pending", data: { status: "pending" } };
$http.get("/rest/searchconsole/cluster_create?websiteid=" + h.siteId()).then(function (response) {
$scope.getReportData();
});
};
$scope.fnStartCustomClustering = function () {
$scope.clusterNeedsRefresh = false;
$scope.analytics.second = { action: "pending", data: { status: "pending" } };
$http.get("/rest/searchconsole/cluster_create_custom?websiteid=" + h.siteId()).then(function (response) {
// refresh after 5 seconds to allow backend to start processing
$timeout(function () {
$scope.getReportData();
}, 5000);
});
};
$scope.fnUpdateClusterOptions = function () {
$http
.post("/rest/searchconsole/cluster_update_options", {
websiteid: h.siteId(),
clusterValueKey: $scope.clusterValue.key,
})
.then(function (response) {
// Optionally refresh data or show success message
$scope.getReportData();
});
};
// Poll cluster status every 10 seconds when action is pending
$scope.startClusterPolling = function () {
// Prevent multiple polling intervals
if ($scope.clusterPollingInterval) {
$timeout.cancel($scope.clusterPollingInterval);
}
var poll = function () {
$http
.get("/rest/searchconsole/cluster_get_names?websiteid=" + h.siteId())
.then(function (response) {
if (response.data && response.data.data) {
var status = response.data.data.status;
// Ensure analytics.second and its data object exist
$scope.analytics.second = $scope.analytics.second || {};
$scope.analytics.second.data = $scope.analytics.second.data || {};
// Update data on scope
$scope.analytics.second.data.cluster_metrics = response.data.data.cluster_metrics;
$scope.analytics.second.data.status = status;
// Update progress: increment by 5 until 80%, then by 1, capped at 99
if ($scope.clusterProgress < 80) {
$scope.clusterProgress = Math.min($scope.clusterProgress + 5, 80);
} else if ($scope.clusterProgress < 99) {
$scope.clusterProgress = Math.min($scope.clusterProgress + 1, 99);
}
// If status is no longer clustering or pending, stop polling
if (status === "completed") {
$timeout.cancel($scope.clusterPollingInterval);
$scope.clusterPollingInterval = null;
// call getReportData to refresh data
$scope.getReportData();
} else {
// Otherwise, continue polling
$scope.clusterPollingInterval = $timeout(poll, 10000);
}
}
})
.catch(function (error) {
// Stop polling on error to prevent infinite loops
$timeout.cancel($scope.clusterPollingInterval);
});
};
// Start the first poll immediately
poll();
};
$controller("coreapi", { $scope: $scope });
$scope.init();
$scope.table.itemsperpage = 140;
// Start polling immediately if the initial state is pending or clustering
if ($scope.analytics && $scope.analytics.second && ($scope.analytics.second.action === "pending" || ($scope.analytics.second.data && ($scope.analytics.second.data.status === "clustering" || $scope.analytics.second.data.status === "pending")))) {
$scope.startClusterPolling();
}
},
]);
/**
* Calculates a weighted visibility score for a given query.
* @param {object} query - The query object from your database, now including a 'status' field.
* @param {string} userDomain - The domain of the user's site.
* @param {Array} providers - An array of all active providers.
* @returns {object} - An object containing the score, status, and ranks.
*/
function calculateVisibilityScore(query, userDomain, providers) {
// Directly use the status from the API.
if (query.status === 'pending') {
return {
score: 0,
status: 'Pending',
ranks: {}
};
}
// The rest of the logic only runs if the status is 'done'.
const processed = {
score: 0,
status: 'Gap', // Default status for a 'done' query with no visibility
ranks: {}
};
const getPointsForRank = (rank) => {
if (rank === null) return 0;
if (rank === 1) return 10;
if (rank === 2) return 9.5;
if (rank === 3) return 8.5;
if (rank >= 4 && rank <= 5) return 8;
if (rank >= 6 && rank <= 10) return 7;
if (rank >= 11 && rank <= 15) return 6;
return 0;
};
let totalPoints = 0;
const cleanUserDomain = userDomain.replace(/^www\./, '');
let latestHistory = {};
(query.history || []).forEach(entry => {
if (!latestHistory[entry.provider] || new Date(entry.date) > new Date(latestHistory[entry.provider].date)) {
latestHistory[entry.provider] = entry;
}
});
providers.forEach(provider => {
const entry = latestHistory[provider];
let rank = null;
if (entry && entry.urls) {
const foundIndex = entry.urls.findIndex(url => {
try {
const urlDomain = url.replace(/^https?:\/\//, '').split('/')[0].replace(/^www\./, '');
return urlDomain === cleanUserDomain;
} catch (e) { return false; }
});
if (foundIndex !== -1) {
rank = foundIndex + 1;
}
}
processed.ranks[provider] = rank;
totalPoints += getPointsForRank(rank);
});
if (providers.length > 0) {
processed.score = totalPoints / providers.length;
}
if (processed.score >= 7) {
processed.status = 'Leading';
} else if (processed.score >= 3) {
processed.status = 'Visible';
} else if (processed.score > 0) {
processed.status = 'At Risk';
}
return processed;
}
app.controller("llmvisibility", [
"$scope",
"$http",
"$rootScope",
"$filter",
"h",
"$sce",
function ($scope, $http, $rootScope, $filter, h, $sce) {
$scope.queries = [];
$scope.kpis = {
overallVisibility: 0,
queriesLeading: 0,
visibilityTrend: 0, // Placeholder
topCompetitor: "N/A",
contentGaps: 0,
queriesWithMentions: 0, // ++ ADD THIS
};
$scope.page = {
newQuery: "",
filter: {},
sortType: "q",
sortReverse: false,
// New fields for add form
newQueryExtraDomains: '',
newQueryBrands: ''
};
// Add a new query to track (with error handling and multi-query split)
$scope.fnAddQuery = function () {
if (!$scope.page.newQuery || !$scope.page.newQuery.trim()) {
return; // Don't add empty queries
}
if ($scope.page.isOverBudget) {
$scope.page.error = "This query exceeds your available credits. Please adjust the providers or frequency.";
return;
}
// If we're editing an existing query, perform update instead of add
if ($scope.page.editing) {
var updateQ = $scope.page.newQuery.trim();
var extradomains = ($scope.page.newQueryExtraDomains || '').split(/[,\s]+/).map(function (d) { return (d || '').trim(); }).filter(Boolean);
var brands = ($scope.page.newQueryBrands || '').split(/[,\s]+/).map(function (d) { return (d || '').trim(); }).filter(Boolean);
$http.post('/rest/llmvisibility/update', {
_id: $scope.page.editing,
q: updateQ,
websiteid: h.siteId(),
extradomains: extradomains,
brands: brands,
providers: Object.keys($scope.page.newQueryProviders).filter(k => $scope.page.newQueryProviders[k]),
frequency: $scope.page.newQueryFrequency
}).then(function(res) {
$scope.getQueries();
$scope.fnCancelEdit();
}, function(err) {
var msg = (err.data && (err.data.error || err.data.message)) ? (err.data.error || err.data.message) : 'Failed to update query.';
$scope.page.error = msg;
});
return;
}
// Split by comma or newline
var queries = $scope.page.newQuery.split(/[\,\n]+/);
var errorOccurred = false;
$scope.page.error = null;
// Prepare domains and brands
var extradomains = ($scope.page.newQueryExtraDomains || '').split(/[,\s]+/).map(function (d) { return (d || '').trim(); }).filter(Boolean);
var brands = ($scope.page.newQueryBrands || '').split(/[,\s]+/).map(function (d) { return (d || '').trim(); }).filter(Boolean);
var addNext = function (index) {
if (index >= queries.length) {
$scope.fnCancelEdit(); // Reset form on completion
return;
}
var query = queries[index].trim();
if (!query) {
addNext(index + 1);
return;
}
$http.post("/rest/llmvisibility/add", {
q: query,
websiteid: h.siteId(),
extradomains: extradomains,
brands: brands,
providers: Object.keys($scope.page.newQueryProviders).filter(k => $scope.page.newQueryProviders[k]),
frequency: $scope.page.newQueryFrequency
}).then(function successCallback(res) {
$scope.getQueries();
addNext(index + 1);
}, function errorCallback(err) {
var msg = (err.data && (err.data.error || err.data.message)) ? (err.data.error || err.data.message) : 'Failed to add query "' + query + '".';
if (!errorOccurred) {
$scope.page.error = msg;
errorOccurred = true;
}
// Stop on first error
});
};
addNext(0);
};
// Retrieve the list of all queries for the site
$scope.getQueries = function () {
$http
.get("/rest/llmvisibility/list", {
params: {
websiteid: h.siteId(),
},
}) // site_id is handled by backend
.then(function successCallback(res) {
console.log("LLM Visibility Queries:", res.data);
$scope.processData(res.data);
});
};
// Remove a query
$scope.fnRemoveQuery = function (query) {
if (!confirm('Are you sure you want to remove this query?')) return;
$http.post('/rest/llmvisibility/delete', {
_id: query._id
}).then(function () {
$scope.getQueries();
});
};
// // Edit a query (inline prompt -> update API)
// $scope.fnEditQuery = function (query) {
// // Prevent editing pending queries that may be in-flight
// var newQ = prompt('Edit query', query.q);
// if (newQ === null) return; // user cancelled
// newQ = ('' + newQ).trim();
// if (!newQ) return alert('Query cannot be empty.');
// $http.post('/rest/llmvisibility/update', {
// _id: query._id,
// q: newQ
// }).then(function (res) {
// $scope.getQueries();
// }, function (err) {
// var msg = (err.data && (err.data.error || err.data.message)) ? (err.data.error || err.data.message) : 'Failed to update query.';
// alert(msg);
// });
// };
// Edit a query: populate the add form and set editing state
$scope.fnEditQuery = function (query) {
$scope.page.newQuery = query.q || '';
// try multiple possible field names stored on the query object
var existingExtra = query.extradomains || query.extraDomains || query.extra_domains || query.domains || '';
var existingBrands = query.brands || query.brand || '';
if (Array.isArray(existingExtra)) existingExtra = existingExtra.join(', ');
if (Array.isArray(existingBrands)) existingBrands = existingBrands.join(', ');
$scope.page.newQueryExtraDomains = existingExtra || '';
$scope.page.newQueryBrands = existingBrands || '';
// -- Populate new fields for editing --
$scope.page.newQueryFrequency = query.frequency || 'weekly';
// Reset providers, then set the ones from the query
$scope.page.newQueryProviders = { google: false, openai: false, perplexity: false };
if (query.providers && query.providers.length) {
query.providers.forEach(p => {
if ($scope.page.newQueryProviders.hasOwnProperty(p)) {
$scope.page.newQueryProviders[p] = true;
}
});
} else {
// Default if not set
$scope.page.newQueryProviders = { google: true, openai: true, perplexity: true };
}
$scope.page.editing = query._id;
// focus the textarea
setTimeout(function(){ try{ var el = document.querySelector('textarea[ng-model="page.newQuery"]'); if(el) el.focus(); }catch(e){} },50);
};
// Cancel editing and reset the add form
$scope.fnCancelEdit = function() {
$scope.page.editing = null;
$scope.page.newQuery = '';
$scope.page.newQueryExtraDomains = $rootScope.user && $rootScope.user.site ? $rootScope.user.site.domain.replace(/^www\./,'') : '';
$scope.page.newQueryBrands = $rootScope.user && $rootScope.user.site && $rootScope.user.site.brand ? $rootScope.user.site.brand : '';
$scope.page.error = null;
// -- Reset new fields --
$scope.page.newQueryProviders = { google: true, openai: true, perplexity: true };
$scope.page.newQueryFrequency = 'weekly';
};
// Helper: Find the rank (1-based) of the user's domain in a list of URLs
function findRankInUrls(urls, domain) {
if (!Array.isArray(urls) || !domain) return null;
for (var i = 0; i < urls.length; i++) {
try {
var urlDomain = urls[i].replace(/^https?:\/\//, '').split('/')[0].replace(/^www\./, '');
if (urlDomain === domain.replace(/^www\./, '')) {
return i + 1;
}
} catch (e) { }
}
return null;
}
// Helper: Extract domain from a URL
function getDomainFromUrl(url) {
if (!url) return null;
try {
return url.replace(/^https?:\/\//, '').split('/')[0].replace(/^www\./, '');
} catch (e) { return null; }
}
// Main data processing function
$scope.processData = function (data) {
const userDomain = $rootScope.user.site.domain.replace(/^www\./, '');
let totalVisibilityScore = 0;
let leadingQueriesCount = 0;
let contentGapsCount = 0;
let allProvidersMap = {};
// Note: The brand mention logic is kept here to ensure compatibility
// if you have already implemented it.
let queriesWithMentions = 0;
// ++ Variables for trend calculation
let totalPastVisibilityScore = 0;
let trendCalculableQueries = 0;
// Competitor scoring system setup
let competitorPoints = {};
let competitorAppearances = {};
const getPointsForRank = (rank) => {
if (rank === null) return 0;
if (rank === 1) return 10; if (rank === 2) return 8; if (rank === 3) return 7;
if (rank >= 4 && rank <= 5) return 5; if (rank >= 6 && rank <= 10) return 3;
if (rank >= 11 && rank <= 15) return 1; return 0;
};
const getDomainFromUrl = (url) => {
if (!url) return null;
try {
return url.replace(/^https?:\/\//, '').split('/')[0].replace(/^www\./, '');
} catch (e) { return null; }
};
// Determine all unique providers from the data
data.forEach(function (query) {
(query.history || []).forEach(function (entry) {
allProvidersMap[entry.provider] = true;
});
});
const allProviders = Object.keys(allProvidersMap).sort();
$scope.allProviders = allProviders;
// Process each query
let visibleQueriesCount = 0;
data.forEach(function (query) {
// ++ Set defaults for existing queries without provider/frequency info ++
if (!query.providers || query.providers.length === 0) {
query.providers = allProviders;
}
if (!query.frequency) {
query.frequency = 'weekly';
}
query.processed = calculateVisibilityScore(query, userDomain, allProviders);
// Individual query sparkline logic (remains unchanged)
let historyByDate = {};
(query.history || []).forEach(function (entry) {
if (!entry.date) return;
var dateStr = entry.date.substr(0, 10);
if (!historyByDate[dateStr]) historyByDate[dateStr] = [];
var rank = null;
if (entry.urls && entry.urls.length) {
for (var i = 0; i < entry.urls.length; ++i) {
var urlDomain = entry.urls[i].replace(/^https?:\/\//, '').split('/')[0].replace(/^www\./, '');
if (urlDomain === userDomain) { rank = i + 1; break; }
}
}
if (rank !== null) historyByDate[dateStr].push(rank);
});
var trendArr = [];
Object.keys(historyByDate).forEach(function (d) {
var arr = historyByDate[d];
if (arr.length) {
var avg = arr.reduce(function (a, b) { return a + b; }, 0) / arr.length;
trendArr.push({ date: d, rank: avg });
}
});
trendArr.sort(function (a, b) { return a.date.localeCompare(b.date); });
if (trendArr.length > 1) {
var minRank = Math.min.apply(null, trendArr.map(function (t) { return t.rank; }));
var maxRank = Math.max.apply(null, trendArr.map(function (t) { return t.rank; }));
if (minRank === maxRank) { minRank = 1; maxRank = minRank + 1; }
var w = 60, h = 18, n = trendArr.length;
query.trendline = trendArr.map(function (t, i) {
var x = Math.round(i * (w / (n - 1)));
var y = Math.round(h - ((t.rank - minRank) / (maxRank - minRank)) * h);
return x + ',' + y;
}).join(' ');
} else {
query.trendline = '';
}
// ++ START: NEW BRAND MENTION LOGIC ++
query.processed.brandMentions = {}; // Object to store mentions by provider
let queryHasAnyMention = false;
const brandsToTrack = query.brands || [];
if (brandsToTrack.length > 0) {
let latestHistory = {};
(query.history || []).forEach(function (entry) {
if (!latestHistory[entry.provider] || new Date(entry.date) > new Date(latestHistory[entry.provider].date)) {
latestHistory[entry.provider] = entry;
}
});
// Create a case-insensitive regex with word boundaries
const brandRegex = new RegExp('\\b(' + brandsToTrack.join('|') + ')\\b', 'gi');
for (const provider in latestHistory) {
const entry = latestHistory[provider];
if (entry.responseText && entry.responseText.match(brandRegex)) {
query.processed.brandMentions[provider] = true;
queryHasAnyMention = true;
} else {
// Explicitly set to false if no mention is found for a provider with a response
if (entry.responseText) {
query.processed.brandMentions[provider] = false;
}
}
}
}
if (queryHasAnyMention) {
queriesWithMentions++;
}
// ++ END: NEW BRAND MENTION LOGIC ++
// Only count non-pending queries for KPIs and visibility
if (query.status !== 'pending') {
totalVisibilityScore += query.processed.score;
visibleQueriesCount++;
if (query.processed.status === "Leading") leadingQueriesCount++;
else if (query.processed.status === "Gap") contentGapsCount++;
}
// ++ START: Overall Trend Calculation Logic ++
if (query.history && query.history.length > 0) {
let queryHistoryByDate = {};
query.history.forEach(entry => {
const dateStr = entry.date.substr(0, 10);
if (!queryHistoryByDate[dateStr]) queryHistoryByDate[dateStr] = [];
queryHistoryByDate[dateStr].push(entry);
});
const sortedDates = Object.keys(queryHistoryByDate).sort();
if (sortedDates.length > 1) {
const oldestDate = sortedDates[0];
let oldestLatestHistory = {};
queryHistoryByDate[oldestDate].forEach(entry => {
if (!oldestLatestHistory[entry.provider] || new Date(entry.date) > new Date(oldestLatestHistory[entry.provider].date)) {
oldestLatestHistory[entry.provider] = entry;
}
});
const pastQuery = { status: 'done', history: Object.values(oldestLatestHistory) };
const pastResult = calculateVisibilityScore(pastQuery, userDomain, allProviders);
totalPastVisibilityScore += pastResult.score;
trendCalculableQueries++;
}
}
// ++ END: Overall Trend Calculation Logic ++
// Competitor Logic (remains unchanged)
let latestHistory = {};
(query.history || []).forEach(function (entry) {
if (!latestHistory[entry.provider] || new Date(entry.date) > new Date(latestHistory[entry.provider].date)) {
latestHistory[entry.provider] = entry;
}
});
for (const provider in latestHistory) {
const entry = latestHistory[provider];
const seenDomains = new Set();
(entry.urls || []).forEach((url, index) => {
const domain = getDomainFromUrl(url);
if (domain && domain !== userDomain) {
if (!competitorPoints[domain]) {
competitorPoints[domain] = 0;
competitorAppearances[domain] = 0;
}
competitorPoints[domain] += getPointsForRank(index + 1);
if (!seenDomains.has(domain)) {
competitorAppearances[domain]++;
seenDomains.add(domain);
}
}
});
}
});
// Final Competitor Score Calculation (remains unchanged)
let topCompetitor = "N/A";
let maxCompetitorScore = -1;
for (const domain in competitorPoints) {
const avgScore = competitorAppearances[domain] > 0 ? competitorPoints[domain] / competitorAppearances[domain] : 0;
if (avgScore > maxCompetitorScore) {
maxCompetitorScore = avgScore;
topCompetitor = domain;
}
}
// Calculate KPIs
$scope.kpis.overallVisibility = visibleQueriesCount > 0 ? (totalVisibilityScore / visibleQueriesCount) : 0;
$scope.kpis.queriesLeading = leadingQueriesCount;
$scope.kpis.contentGaps = contentGapsCount;
$scope.kpis.topCompetitor = topCompetitor;
$scope.kpis.queriesWithMentions = queriesWithMentions;
// ++ ADD FINAL TREND CALCULATION ++
let visibilityTrend = 0;
if (trendCalculableQueries > 0 && visibleQueriesCount > 0) {
// Use the current average, not the sum for trendable queries
const currentAverage = totalVisibilityScore / visibleQueriesCount;
const pastAverage = totalPastVisibilityScore / trendCalculableQueries;
if (pastAverage > 0) {
visibilityTrend = ((currentAverage - pastAverage) / pastAverage) * 100;
} else if (currentAverage > 0) {
visibilityTrend = 100; // From 0 to something positive is a 100% gain for our purposes
}
}
$scope.kpis.visibilityTrend = visibilityTrend;
$scope.queries = data;
};
// REVISED Toggle for details view
$scope.fnQueryHistory = function (query) {
query.active = !query.active;
// Only process the data if we are opening the row for the first time
if (query.active && !query.details) {
const userDomain = $rootScope.user.site.domain.replace(/^www\./, '');
const brandsToTrack = query.brands || [];
// Setup for text highlighting
const highlightTerms = [userDomain, ...brandsToTrack].filter(Boolean);
const highlightRegex = new RegExp('\\b(' + highlightTerms.join('|') + ')\\b', 'gi');
query.details = {
responses: [],
competitors: []
// The separate brandSummary is no longer needed
};
const getPointsForRank = (rank) => {
if (rank === null) return 0;
if (rank === 1) return 10; if (rank === 2) return 8; if (rank === 3) return 7;
if (rank >= 4 && rank <= 5) return 5; if (rank >= 6 && rank <= 10) return 3;
if (rank >= 11 && rank <= 15) return 1; return 0;
};
let competitorPoints = {};
let providerCount = 0;
// Get the most recent entry for each provider
let latestHistory = {};
(query.history || []).forEach(function (entry) {
if (!latestHistory[entry.provider] || new Date(entry.date) > new Date(latestHistory[entry.provider].date)) {
latestHistory[entry.provider] = entry;
}
});
for (const provider in latestHistory) {
providerCount++;
const entry = latestHistory[provider];
// 1. Prepare LLM Response Text (with highlighting)
if (entry.responseText) {
let highlightedText = entry.responseText.replace(highlightRegex, `$&`);
highlightedText = highlightedText
.replace(/\*\*(.*?)\*\*/g, '$1')
.replace(/\*(.*?)\*/g, '$1')
.replace(/`([^`]+)`/g, '$1')
.replace(/\[(.*?)\]\((.*?)\)/g, '$1')
.replace(/\n{3,}/g, '\n\n');
// ++ START: NEW MENTION DETECTION LOGIC ++
let mentionedBrands = [];
if (brandsToTrack.length > 0) {
brandsToTrack.forEach(brand => {
const brandRegex = new RegExp('\\b' + brand + '\\b', 'gi');
if (brandRegex.test(entry.responseText)) {
mentionedBrands.push(brand);
}
});
}
// ++ END: NEW MENTION DETECTION LOGIC ++
query.details.responses.push({
provider: provider,
text: $sce.trustAsHtml(`${highlightedText}
`),
urls: entry.urls || [],
mentionedBrands: mentionedBrands // ++ ATTACH MENTIONED BRANDS
});
}
// 2. Calculate points for each competitor in this provider's list
(entry.urls || []).forEach(function (url, index) {
const domain = getDomainFromUrl(url);
if (domain && domain !== userDomain) {
if (!competitorPoints[domain]) {
competitorPoints[domain] = 0;
}
competitorPoints[domain] += getPointsForRank(index + 1);
}
});
}
// 3. Calculate final score for each competitor and sort
for (const domain in competitorPoints) {
query.details.competitors.push({
domain: domain,
score: providerCount > 0 ? competitorPoints[domain] / providerCount : 0
});
}
query.details.competitors.sort((a, b) => b.score - a.score);
// 4. Name clusters using AJAX calls to /name-single-cluster/
query.details.clusterNames = {};
const nameClusters = async () => {
// Group keywords by cluster for naming
const clustersForNaming = {};
query.details.responses.forEach(response => {
if (response.urls && response.urls.length > 0) {
response.urls.forEach((url, index) => {
const keyword = url.replace(/^https?:\/\//, '').split('/')[0].replace(/^www\./, '');
// For now, we'll use a simple clustering approach or you can integrate with your clustering API
// This is a placeholder - you'll need to implement the actual clustering logic
const clusterId = Math.floor(Math.random() * 5); // Placeholder clustering
if (!clustersForNaming[clusterId]) {
clustersForNaming[clusterId] = [];
}
clustersForNaming[clusterId].push(keyword);
});
}
});
// Make AJAX calls for each cluster
for (const [clusterId, keywords] of Object.entries(clustersForNaming)) {
try {
const response = await $http.post('http://135.125.202.210:8000/name-single-cluster/', {
keywords: keywords
});
query.details.clusterNames[clusterId] = response.data.name || `Cluster ${clusterId}`;
} catch (error) {
console.error('Error naming cluster', clusterId, error);
query.details.clusterNames[clusterId] = `Cluster ${clusterId}`;
}
}
// Trigger digest cycle to update UI
$scope.$apply();
};
// Start naming clusters asynchronously
nameClusters();
// --- Render AmCharts historical chart ---
// ... (existing AmCharts logic remains unchanged) ...
setTimeout(function () {
if (!window.AmCharts) return;
var chartData = [];
var providers = $scope.allProviders || [];
var dateMap = {};
(query.history || []).forEach(function (entry) {
if (!entry.date) return;
var dateStr = entry.date.substr(0, 10).replace(/-/g, '');
dateMap[dateStr] = dateMap[dateStr] || { date: dateStr };
var rank = null;
if (entry.urls && entry.urls.length) {
for (var i = 0; i < entry.urls.length; ++i) {
var urlDomain = entry.urls[i].replace(/^https?:\/\//, '').split('/')[0].replace(/^www\./, '');
if (urlDomain === userDomain) {
rank = i + 1;
break;
}
}
}
dateMap[dateStr][entry.provider] = rank;
});
for (var d in dateMap) chartData.push(dateMap[d]);
chartData.sort(function (a, b) { return a.date.localeCompare(b.date); });
var graphs = (providers || []).map(function (provider, idx) {
return {
balloonText: provider + ': [[value]]',
title: provider,
valueField: provider,
bullet: 'round',
bulletBorderAlpha: 1,
bulletColor: '#FFFFFF',
useLineColorForBulletBorder: true,
fillAlphas: 0,
lineThickness: 2,
lineAlpha: 1,
bulletSize: 7,
lineColor: h.colors ? h.colors()[idx % 6] : undefined
};
});
AmCharts.makeChart('llm-history-chart-' + query._id, {
type: 'serial', theme: 'light', marginLeft: 0, marginRight: 0, marginTop: 10, categoryField: 'date', rotate: false, dataDateFormat: 'YYYYMMDD',
categoryAxis: { gridPosition: 'start', parseDates: true, labelRotation: 45 },
valueAxes: [{ reversed: true, position: 'left', axisAlpha: 0, labelsEnabled: true, minimum: 1 }],
graphs: graphs, dataProvider: chartData, legend: { position: 'bottom', enabled: true, useGraphSettings: true }
});
}, 0);
}
};
// --- Credit & Budgeting ---
$scope.providers = ['google', 'openai', 'perplexity'];
$scope.frequencies = {
'daily': { name: 'Daily', cost: 31 },
'weekly': { name: 'Weekly', cost: 4 },
'monthly': { name: 'Monthly', cost: 1 }
};
// Initialize new query options
$scope.page.newQueryProviders = { google: true, openai: true, perplexity: true };
$scope.page.newQueryFrequency = 'weekly';
$scope.page.newQueryCost = 0;
$scope.page.isOverBudget = false;
$scope.page.currentUsage = 0;
$scope.page.totalProjectedCost = 0;
// Function to calculate cost
$scope.calculateCost = function() {
// Count the number of actual queries entered in the textarea
var queryCount = 0;
if ($scope.page.newQuery && $scope.page.newQuery.trim()) {
// Split by comma or newline and filter out any empty strings
queryCount = $scope.page.newQuery.split(/[\,\n]+/).filter(q => q.trim()).length;
}
// When editing, we are always dealing with a single query, so force count to 1
if ($scope.page.editing) {
queryCount = 1;
}
// 1. Calculate cost of the new/edited query batch
var newQueryProvidersCount = Object.values($scope.page.newQueryProviders).filter(Boolean).length;
var newQueryFrequencyCost = $scope.frequencies[$scope.page.newQueryFrequency] ? $scope.frequencies[$scope.page.newQueryFrequency].cost : 0;
var newQueryCost = queryCount * newQueryProvidersCount * newQueryFrequencyCost;
$scope.page.newQueryCost = newQueryCost;
// 2. Calculate current usage from all existing queries
var currentUsage = 0;
($scope.queries || []).forEach(function(q) {
// If we are editing this query, its cost will be replaced by the new cost, so skip it here.
if ($scope.page.editing && $scope.page.editing === q._id) {
return;
}
var providerCount = (q.providers && q.providers.length) ? q.providers.length : $scope.allProviders.length;
var frequencyCost = $scope.frequencies[q.frequency] ? $scope.frequencies[q.frequency].cost : $scope.frequencies['weekly'].cost;
currentUsage += providerCount * frequencyCost;
});
$scope.page.currentUsage = currentUsage;
// 3. Calculate total projected cost
var totalProjectedCost = currentUsage + newQueryCost;
$scope.page.totalProjectedCost = totalProjectedCost;
// 4. Check against budget
var availableCredits = ($rootScope.user && $rootScope.user.credits && $rootScope.user.credits.llm) ? $rootScope.user.credits.llm : 0;
$scope.page.isOverBudget = totalProjectedCost > availableCredits;
};
// Watch for changes to recalculate cost
$scope.$watch('page.newQueryProviders', $scope.calculateCost, true);
$scope.$watch('page.newQueryFrequency', $scope.calculateCost);
$scope.$watch('queries', $scope.calculateCost, true); // Recalculate if queries are added/deleted
// Initial calculation
$scope.calculateCost();
// Initial load
$scope.getQueries();
},
]);
app.controller('keywordtool', ['$scope', '$http', '$rootScope', '$filter', '$location', 'h',
function($scope, $http, $rootScope, $filter, $location, h) {
$scope.suggest = { 'q': '', result: [] };
$scope.order = {};
$scope.fnOrder = function(field) {
var reverse = (field == $scope.order.field) ? true : false;
$scope.order = { 'field': field, 'reverse': reverse };
}
var lang = {
"de": [
{ key: 'was', 'value': 'was ', 'type': 'vraag' },
{ key: 'was ist', 'value': 'was ist ', 'type': 'vraag' },
{ key: 'wie', 'value': 'wie ', 'type': 'vraag' },
{ key: 'wann', 'value': 'wann ', 'type': 'vraag' },
{ key: 'welche', 'value': 'welche ', 'type': 'vraag' },
{ key: 'warum', 'value': 'warum ', 'type': 'vraag' },
],
"nl": [
{ key: 'wat', 'value': 'wat ', 'type': 'vraag' },
{ key: 'wat is', 'value': 'wat is ', 'type': 'vraag' },
{ key: 'hoe', 'value': 'hoe ', 'type': 'vraag' },
{ key: 'wanneer', 'value': 'wanneer ', 'type': 'vraag' },
{ key: 'welke', 'value': 'welke ', 'type': 'vraag' },
{ key: 'waarom', 'value': 'waarom ', 'type': 'vraag' },
],
"en": [
{ key: 'what', 'value': 'what ', 'type': 'vraag' },
{ key: 'what is', 'value': 'what is ', 'type': 'vraag' },
{ key: 'how', 'value': 'how ', 'type': 'vraag' },
{ key: 'when', 'value': 'when ', 'type': 'vraag' },
{ key: 'which', 'value': 'which ', 'type': 'vraag' },
{ key: 'why', 'value': 'why ', 'type': 'vraag' },
]
}
var letters = [
{ key: 'a', 'value': ' a' },
{ key: 'b', 'value': ' b' },
{ key: 'c', 'value': ' c' },
{ key: 'd', 'value': ' d' },
{ key: 'e', 'value': ' e' },
{ key: 'f', 'value': ' f' },
{ key: 'g', 'value': ' g' },
{ key: 'h', 'value': ' h' },
{ key: 'i', 'value': ' i' },
{ key: 'j', 'value': ' j' },
{ key: 'k', 'value': ' k' },
{ key: 'l', 'value': ' l' },
{ key: 'm', 'value': ' m' },
{ key: 'n', 'value': ' n' },
{ key: 'o', 'value': ' o' },
{ key: 'p', 'value': ' p' },
{ key: 'q', 'value': ' q' },
{ key: 'r', 'value': ' r' },
{ key: 's', 'value': ' s' },
{ key: 't', 'value': ' t' },
{ key: 'u', 'value': ' u' },
{ key: 'v', 'value': ' v' },
{ key: 'w', 'value': ' w' },
{ key: 'x', 'value': ' x' },
{ key: 'y', 'value': ' y' },
{ key: 'z', 'value': ' z' },
]
$scope.letters = lang[$rootScope.user.site.locale.lang].concat(letters);
var current = { 'key': '', value: '' }
$scope.fnTrackKeyword = function(kw) {
$http.post('/rest/rank-tracker/add', {
keywords: kw,
websiteid: h.siteId()
}).then(function successCallback(res) {
});
}
$scope.fnExploreKeyword = function(q) {
$scope.q = q;
$scope.showsuggesties = true;
$scope.suggest.result = [];
current = { 'key': 'base', value: '' }
$http.get('/rest/keywordtool/get', {
params: {
q: $scope.q,
l: 'long',
}
}).then(function successCallback(res) {
$scope.suggest.result['base'] = res.data;
});
$scope.kwplanner = false;
var next = false;
var go = false;
var mbreak = true;
$http.get('/rest/googlexads/keywordplanner', {
params: {
q: $scope.q,
cc: $scope.user.site.locale.cc,
lc: $scope.user.site.locale.lc,
}
}).then(function successCallback(res) {
$scope.kwplanner = res.data;
});
angular.forEach($scope.letters, function(value, key) {
if (value.type == 'vraag') {
$http.get('/rest/keywordtool/get', {
params: {
q: value.value + $scope.q,
cc: $scope.user.site.locale.cc,
lc: $scope.user.site.locale.lc,
}
}).then(function successCallback(res) {
if (res.data[1]) {
$scope.suggest.result[value.key] = res.data;
}
});
} else {
$http.get('/rest/keywordtool/get', {
params: {
q: $scope.q + value.value,
cc: $scope.user.site.locale.cc,
lc: $scope.user.site.locale.lc,
}
}).then(function successCallback(res) {
if (res.data[1]) {
$scope.suggest.result[value.key] = res.data;
}
});
}
});
}
$scope.fnDownload = function(text) {
var text = '';
for (var i in $scope.suggest.result) {
var res = $scope.suggest.result[i];
for (var y in res) {
var woord = res[y];
if (woord.check) {
console.log(woord);
text = text + woord.r + "\r\n";
}
}
}
for (var i in $scope.kwplanner) {
if ($scope.kwplanner[i].check) {
text = text + $scope.kwplanner[i].keyword + " (" + $scope.kwplanner[i].vol + "," + $scope.kwplanner[i].seo + "," + $scope.kwplanner[i].cpc + ")\r\n";
}
}
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', 'zoekwoord suggesties voor ' + $scope.q + '.txt');
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
}
]);
app.controller('onpagestatus', ['$scope', '$timeout', '$http', '$rootScope', '$location', '$filter', 'h',
function($scope, $timeout, $http, $rootScope, $location, $filter, h) {
$scope.page = {};
var _cb = {
getstarted: function() {
$scope.page.include = 'index.getstarted';
},
crawling: function() {
$scope.page.crawling = true;
$scope.page.include = 'index.list';
},
ready: function() {
$scope.page.include = 'index.list';
}
}
var timeout = 5000
var getStatus = function() {
if ($rootScope.onpagestatus && $rootScope.onpagestatus.data.status == 'done') {
_cb[$rootScope.onpagestatus.cb]($rootScope.onpagestatus.data)
} else {
$http.get('/rest/onpage/status', {
params: {
websiteid: h.siteId(),
}
}).then(function successCallback(res) {
$rootScope.onpagestatus = res.data;
_cb[res.data.cb](res.data);
if ($rootScope.onpagestatus && $rootScope.onpagestatus.data.status == 'crawling') {
$scope.$broadcast('refreshonpagelist');
$timeout.cancel($rootScope.to);
$rootScope.to = $timeout(function() {
timeout = timeout * 1.2;
getStatus();
}, timeout);
}
});
}
}
getStatus();
$scope.fnCrawl = function() {
$http.get('/rest/onpage/crawl', {
params: {
websiteid: h.siteId(),
}
}).then(function successCallback(res) {
$rootScope.onpagestatus.data.status = 'crawling';
$timeout(function() {
getStatus();
}, 2000);
$location.path('/onpage-seo/all');
});
}
$scope.fnEmpty = function() {
$http.get('/rest/onpage/empty', {
params: {
websiteid: h.siteId(),
}
});
}
}
]);
app.filter('zeroplus', function() {
return function(items, timingevent) {
return items.reduce(function(acc, cur) {
if (typeof cur.properties == 'object' && typeof cur.properties.timing == 'object' && cur.properties.timing[timingevent] > 0) {
acc.push(cur);
}
return acc;
}, []);
};
});
app.controller('onpagelist', ['$scope', '$timeout', '$http', '$rootScope', '$filter', 'h',
function($scope, $timeout, $http, $rootScope, $filter, h) {
$scope.page = { 'curllabel': '', 'newurl': $rootScope.user.site.domain + '/' };
$scope.page.table = {
'curpage': 0,
'itemsperpage': 34,
'offset': 0,
'rulefilter': 'all',
'filter': {},
'min': { 'opi': 100, 'score': 100 },
'order': { 'k': 'url', 'r': false },
'fnOrder': function(k, b) {
this.order.r = (k == this.order.k) ? !this.order.r : b;
this.order.k = k;
},
fnSetOffset: function() {
this.offset = this.itemsperpage * (this.curpage - 1);
},
fnFilterProperty: function() {
var pages = angular.copy($rootScope._pages);
$scope.pages = $filter('filter')(pages, $scope.page.table.filter);
}
}
$scope.fnPctChange = function(o, n) {
if (n == 0) {
return '0%';
}
var pct = ((n - o) / o) * 100;
var npct = $filter('number')(pct, 2);
if(!isFinite(npct)){
npct = 100;
}
if (pct < 0) {
return '' + npct + '%';
}
return '+ ' + npct + '%';r
}
$scope.fnNewLabel = function(label) {
if ($scope.page.curlabel != label) {
$scope.page.curlabel = label;
return true;
}
return false;
}
// filter pages by rule
$scope.fnFilterRule = function(ruleName) {
$scope.page.table.fnOrder('properties.opi', true)
var pages = angular.copy($rootScope._pages);
pages = $filter('filter')(pages, $scope.page.table.filter);
$scope.rule = $scope.onpage.rules[ruleName];
$scope.pages = $filter('filter')(pages, function(v) { return (v.rules[ruleName] === false) });
$scope.view = 'pages';
var stats = { 'done': 0, 'open': 0, 'total': 0, 'pages': 0 };
for (var i in pages) {
if (typeof pages[i]['rules'][ruleName] != 'undefined') {
stats['pages']++;
stats['total']++;
stats['done'] = (pages[i]['rules'][ruleName]) ? stats['done'] + 1 : stats['done'];
stats['open'] = (pages[i]['rules'][ruleName]) ? stats['open'] : stats['open'] + 1;
}
}
$scope.stats = stats;
}
var fnChanges = {
init: function() {
$http.get('/rest/onpage/changes', {
params: {
websiteid: h.siteId(),
}
}).then(function successCallback(res) {
// filter in controller => data en url weergave
//$scope.changes = $filter('orderBy')(res.data.data,['-date','-url']);
$scope.changes = res.data.data;
$scope.page.table.fnOrder('date', true)
fnChart($rootScope._history, 'all');
fnChanges.stats();
fnChanges.group('date');
});
},
stats: function() {
var stats = [{ 'label': 'change', 'value': 0, 'color': '#808080' }, { 'label': 'done', 'value': 0, 'color': '#4caf50' }, { 'label': 'issue', 'value': 0, 'color': '#ff9800' }];
var totals = {};
for (var i in $scope.changes) {
if ($scope.changes[i].r === true) {
stats[1].value += 1;
} else if ($scope.changes[i].r === false) {
stats[2].value += 1;
} else {
stats[0].value += 1;
}
totals[$scope.changes[i].url] = totals[$scope.changes[i].url] + 1 || 1
}
// total changes etc per page
$scope.page.totals = [];
for (var i in totals) {
$scope.page.totals.push({ 'url': i, 'count': totals[i] })
}
$scope.page.totals = $filter('orderBy')($scope.page.totals, 'count', true).slice(0, 5);
$scope.page.stats = stats;
$scope.piechart = {
data: stats,
type: 'pie',
"precision": 0,
"decimalSeparator": ",",
"thousandsSeparator": ".",
legend: {
enabled: false,
position: 'right',
autoMargins: false,
marginRight: 100,
},
titleField: 'label',
valueField: 'value',
"colorField": "color",
labelRadius: 5,
radius: '42%',
innerRadius: '60%',
labelText: '[[label]]',
}
},
group: function(key) {
var groups = $scope.changes.reduce(function(acc, cur) {
acc[cur.date] = acc[cur.date] || { 'key': new Date(cur.date).toLocaleString('default', { year: 'numeric', 'month': 'long', day: 'numeric' }), 'letter': new Date(cur.date).toLocaleString('default', { 'month': 'short' }).charAt(0).toUpperCase(), 'items': [] };
acc[cur.date].items.push(cur);
return acc;
}, {});
$scope.groups = $filter('orderBy')(Object.values(groups), 'date', false);
}
}
var fnSpeed = function(pages) {
var t = {};
var s = {};
// min max
pages.forEach(function(page, k) {
if (typeof page.properties.timing != 'undefined' && page.properties.timing != null && page.properties.timing.ttfb) {
var _otiming = page.properties.timing;
delete _otiming.cls;
Object.keys(_otiming).forEach(function(timingevent, n) {
s[timingevent] = s[timingevent] || []
if (_otiming[timingevent] > 0) {
s[timingevent].push(_otiming[timingevent])
}
})
}
})
for (timingevent in s) {
var v = s[timingevent];
var min = 0;
var max = Math.max(...v);
var step = Math.round((max - min) / 18);
if (step == 0) {
step = 0.1;
}
var oinit = {}
for (var i = min; i < max; i = i + step) {
oinit[Math.round(i / step) * step] = 0;
}
t[timingevent] = v.reduce(function(acc, cur) {
acc[Math.round(cur / step) * step] = acc[Math.round(cur / step) * step] + 1;
return acc;
}, oinit)
}
// get events from keys
$scope.timingevents = Object.keys(t);
var colors = h.colors()
// create charts
$scope.charts = {};
Object.keys(t).forEach(function(timingevent, k) {
var data = Object.keys(t[timingevent]).map(function(a) {
return { 'timing': a, 'pages': t[timingevent][a] }
})
$scope.charts[timingevent] = {
data: data,
type: 'serial',
categoryField: 'timing',
rotate: false,
legend: {
enabled: false
},
categoryAxis: {
gridPosition: "start",
parseDates: false,
labelRotation: 0,
gridThickness: 0,
labelFunction: function(value, valueText, axis) { return value + 'ms' },
},
valueAxes: [{
position: "left",
axisAlpha: 0,
labelsEnabled: true,
dashLength: 4,
color: '#dedede',
}],
graphs: [{
fillColorsField: "color",
balloonText: "" + timingevent + "
[[category]]ms:
[[value]] pages",
type: "column",
columnWidth: 0.90,
title: "Zoekwoord waarde",
valueField: 'pages',
fillAlphas: 1,
lineColor: colors[k],
autoColor: true,
}],
}
})
}
// AI-crawlers: status per bot voor de site en de pagina's met een blokkade
$scope.oAiBots = {
'loading': false,
'data': null,
init: function(refresh) {
var self = this;
self.loading = true;
$http.get('/rest/onpage/aibots', {
params: {
websiteid: h.siteId(),
refresh: refresh ? 1 : 0
}
}).then(function successCallback(res) {
self.data = res.data;
self.rows = self.fnRows(res.data);
self.loading = false;
}, function errorCallback() {
self.loading = false;
});
},
fnBlocked: function(type) {
return (this.data && this.data.bots || []).filter(function(b) {
return b.type == type && (b.robots_pages > 0 || b.server_blocked);
});
},
// één rij per pagina: AI-signalen en geblokkeerde AI-bots samen, zoals de paginatabel van de scan
'rows': [],
'table': {
'order': '-score', 'filter': { 'url': '' }, 'level': '',
// paginering zoals de paginatabel van de scan
'curpage': 1, 'itemsperpage': 34, 'offset': 0,
fnSetOffset: function() {
this.offset = this.itemsperpage * (this.curpage - 1);
},
fnReset: function() {
this.curpage = 1;
this.offset = 0;
}
},
fnRows: function(data) {
var map = {};
var rows = [];
var row = function(p) {
var id = p._id.$oid;
if (!map[id]) {
map[id] = { '_id': p._id, 'url': p.url, 'score': null, 'level': null, 'top': [], 'blocked': [], 'search': false };
rows.push(map[id]);
}
return map[id];
};
((data.aicontent && data.aicontent.pages) || []).forEach(function(p) {
var r = row(p);
r.score = p.score;
r.level = p.level;
r.top = p.top;
});
(data.pages || []).forEach(function(p) {
var r = row(p);
r.blocked = p.blocked;
r.search = p.search;
});
return rows;
},
fnOrder: function(key) {
this.table.order = (this.table.order == '-' + key) ? key : '-' + key;
this.table.fnReset();
},
fnLevel: function(r) {
var level = $scope.oAiBots.table.level;
return !level || r.level == level;
},
// status van een bot in één woord: robots.txt, server of toegestaan
fnBotStatus: function(b) {
if (b.robots_home) { return 'robots_site'; }
if (b.server_checked && this.data.conclusive && b.server_blocked) { return 'server'; }
if (b.robots_pages > 0) { return 'robots_pages'; }
return 'allowed';
}
};
// calculare rules and scores
var fnDuplicates = function(pages) {
var duplicates = {};
for (var i in pages) {
for (var y in pages[i]['compare']) {
duplicates[y] = duplicates[y] || {};
duplicates[y][pages[i]['compare'][y]] = duplicates[y][pages[i]['compare'][y]] || { 'name': pages[i]['compare'][y], 'count': 0, 'pages': [] };
duplicates[y][pages[i]['compare'][y]].count++;
duplicates[y][pages[i]['compare'][y]].pages.push({ 'serps': pages[i].serps, 'url': pages[i].url, 'properties': pages[i].properties, '_id': pages[i]._id });
}
}
// remove single counts in compare
for (var i in duplicates) {
for (var y in duplicates[i]) {
if (duplicates[i][y].count <= 1) {
delete duplicates[i][y];
}
}
}
var x = {}
for (var i in duplicates) {
x[i] = Object.values(duplicates[i])
}
$scope.duplicates = x;
}
// assistent
var fnAssistent = function(pages) {
$http.get('/rest/onpage/weights').then(function successCallback(weights) {
var issues = [];
for (var i in pages) {
for (var y in pages[i]['rules']) {
if (!pages[i]['rules'][y]) {
if (pages[i]['properties']['pagerank'] && pages[i]['properties']['opi'] && weights.data[y]) {
issues.push({ 'help': false, 'page': pages[i], 'weight': parseFloat(weights.data[y]['f'] * (200 + pages[i]['properties']['pagerank']) * (200 + pages[i]['properties']['opi'])), 'rule': y, 'f': weights.data[y]['f'] })
}
}
}
}
$scope.issues = issues;
});
}
// alerts
$scope.oAlerts = {
init: function() {
$scope.page.notifications = 'pending';
this.getSettings();
$timeout(function() { $scope.oAlerts.firebase.init(); }, 4000)
},
firebase: {
'config': {
apiKey: "AIzaSyAKgRy2MQF2OUPh-WpUYMWOU6M_XijIMxg",
authDomain: "marketingtracer-73fb0.firebaseapp.com",
databaseURL: "https://marketingtracer-73fb0.firebaseio.com",
projectId: "marketingtracer-73fb0",
storageBucket: "marketingtracer-73fb0.appspot.com",
messagingSenderId: "769511864713",
appId: "1:769511864713:web:340f1f811287abb766c958"
},
'init': function() {
firebase.initializeApp(this.config);
this.messaging = firebase.messaging();
this.messaging.usePublicVapidKey("BDRyazo-zYOixCGZOS-ZI87KPiGfDCCFr5J-GMvydQkBVXOU9RaV8dolUZt_9RT1ZXk2WQ54lwKulSXn-r_B3UY");
if (!("Notification" in window)) {
$scope.page.notifications = 'notenabled';
} else if (Notification.permission == "granted") {
this.getToken();
} else {
$scope.page.notifications = 'notgranted';
}
},
'getToken': function() {
this.messaging.getToken().then(function(token) {
$scope.token = token;
console.log($scope.alertnotifications);
if ($scope.alertnotifications.indexOf(token) > -1) {
$scope.page.notifications = 'granted';
} else {
$scope.page.notifications = 'available';
}
$scope.$apply();
console.log("token is : ", token)
})
},
unsubscribe: function() {
$scope.page.notifications = 'available';
$http.post('/rest/onpage/alertsettings.notifications.pull', {
websiteid: h.siteId(),
'token': $scope.token
});
},
subscribe: function() {
$scope.page.notifications = 'granted';
$http.post('/rest/onpage/alertsettings.notifications.save', {
websiteid: h.siteId(),
'token': $scope.token
});
},
'requestPermission': function() {
this.messaging
.requestPermission()
.then(function() {
console.log("Notification permission granted.");
$scope.page.notifications = 'granted';
$scope.$apply();
// get the token in the form of promise
return messaging.getToken()
})
.then(function(token) {
// console.log("token is : ", token)
$http.post('/rest/onpage/alertsettings.notifications.save', {
websiteid: h.siteId(),
'token': token
});
})
.catch(function(err) {
$scope.page.notifications = 'notenabled';
//console.log("Unable to get permission to notify.", err);
});
this.messaging.onMessage(function(payload) {
var notification = new Notification(payload.notification.title, payload.notification);
console.log("Message received. ", payload);
});
},
},
getSettings: function() {
// hardcoded ruleset
$scope.alertrules = [
{ 'rule': 'title_change' },
{ 'rule': 'pfirst_change' },
{ 'rule': 'metadescription_change' },
{ 'rule': 'h1_change' },
{ 'rule': 'title_removed' },
{ 'rule': 'h1_removed' },
{ 'rule': 'metadescription_removed' },
{ 'rule': 'not_indexable' },
{ 'rule': 'status_change' },
{ 'rule': 'total_score_change' },
{ 'rule': 'lighthouse_fail_change' },
];
// users, names and settings
$http.get('/rest/sites/get', {
params: {
'websiteid': h.siteId(),
}
}).then(function successCallback(res) {
if (res.data._id) {
$scope.user.site.users = res.data.users;
$scope.user.site.names = res.data.names;
$scope.alertnotifications = res.data.alertnotifications || [];
$scope.alertsettings = angular.extend({
'title_change': { 'sens': 0, 'users': [], 'active': false },
'pfirst_change': { 'sens': 0, 'users': [], 'active': false },
'metadescription_change': { 'sens': 0, 'users': [], 'active': false },
'h1_change': { 'sens': 0, 'users': [], 'active': false },
'title_removed': { 'sens': 0, 'users': [], 'active': false },
'h1_removed': { 'sens': 0, 'users': [], 'active': false },
'metadescription_removed': { 'sens': 0, 'users': [], 'active': false },
}, res.data.alertsettings);
}
});
},
saveSettings: function() {
$http.post('/rest/onpage/alertsettings.save', {
websiteid: h.siteId(),
'alertsettings': $scope.alertsettings
});
},
addUser: function(userid, rulename) {
var count = 0;
$scope.alertsettings[rulename].users = $scope.alertsettings[rulename].users || [];
for (var i = 0; i < $scope.alertsettings[rulename].users.length; i++) {
if ($scope.alertsettings[rulename].users[i].$oid == userid.$oid) {
$scope.alertsettings[rulename].users.splice(i, 1);
count = count + 1;
}
}
if (count == 0) {
$scope.alertsettings[rulename].users.push(userid);
}
this.saveSettings();
}
}
// calculare rules and scores
var fnRules = function(pages) {
var stats = { 'done': 0, 'open': 0, 'total': 0, 'pages': 0 };
var rules = {};
var compare = {};
for (var i in pages) {
stats['pages']++;
for (var y in pages[i]['rules']) {
if ($scope.onpage.rules[y]) {
stats['total']++;
stats['done'] = (pages[i]['rules'][y]) ? stats['done'] + 1 : stats['done'];
stats['open'] = (pages[i]['rules'][y]) ? stats['open'] : stats['open'] + 1;
rules[y] = rules[y] || { 'done': 0, 'open': 0, 'total': 0 }
rules[y]['done'] = (pages[i]['rules'][y]) ? rules[y]['done'] + 1 : rules[y]['done'];
rules[y]['open'] = (pages[i]['rules'][y]) ? rules[y]['open'] : rules[y]['open'] + 1;
rules[y]['total']++;
rules[y]['name'] = y;
}
}
}
$scope.rules = rules;
$scope.stats = stats;
}
var fnChart = function(history, field) {
$scope.chart = {
data: history,
type: 'serial',
theme: 'light',
marginLeft: 0,
marginRight: 0,
marginTop: 10,
fontFamily: 'Roboto',
categoryField: 'date',
rotate: false,
pathToImages: 'vendor/amchart/images/',
dataDateFormat: 'YYYY-MM-DD',
categoryAxis: {
gridPosition: "start",
parseDates: true,
gridThickness: 0
},
valueAxes: [{
position: "left",
axisAlpha: 0,
labelsEnabled: true,
dashLength: 4,
color: '#dedede',
}],
graphs: [{
balloonText: "[[key]]
[[value]]",
"bullet": "round",
"bulletBorderAlpha": 1,
"bulletColor": "#FFFFFF",
"useLineColorForBulletBorder": true,
"fillAlphas": 0,
"lineThickness": 2,
"lineAlpha": 1,
"bulletSize": 7,
"title": "Expenses",
"valueField": field,
lineColor: '#FFC107',
}],
}
}
var fnInit = function() {
if ($scope.viewrule) {
$scope.view = 'pages';
fnChart($rootScope._history, $scope.viewrule);
if ($scope.viewrule != 'all') {
$scope.fnFilterRule($scope.viewrule);
} else {
// fuck, ach ja ...
var pages = angular.copy($rootScope._pages);
var stats = { 'done': 0, 'open': 0, 'total': 0, 'pages': 0 };
$scope.rule = { 'title': 'All pages', 'textfalse': 'View all pages and scores' }
var compare = {};
for (var i in pages) {
stats['pages']++;
for (var y in pages[i]['rules']) {
stats['total']++;
stats['done'] = (pages[i]['rules'][y]) ? stats['done'] + 1 : stats['done'];
stats['open'] = (pages[i]['rules'][y]) ? stats['open'] : stats['open'] + 1;
}
}
$scope.stats = stats;
}
} else if ($scope.viewrulelabel) {
if ($scope.viewrulelabel == 'assistent') {
$scope.view = 'assistent';
fnAssistent($rootScope._pages);
} else if ($scope.viewrulelabel == 'duplicates') {
$scope.view = 'duplicates';
fnDuplicates($rootScope._pages);
} else if ($scope.viewrulelabel == 'alerts') {
$scope.view = 'alerts';
$scope.oAlerts.init();
} else if ($scope.viewrulelabel == 'changes') {
$scope.view = 'changes';
fnChanges.init();
} else if ($scope.viewrulelabel == 'speed') {
$scope.view = 'speed';
fnSpeed($rootScope._pages);
} else if ($scope.viewrulelabel == 'aibots') {
$scope.view = 'aibots';
$scope.oAiBots.init();
} else {
$scope.view = 'rules';
if ($scope.viewrulelabel != 'all') {
var ret = {};
for (var i in $scope.onpage.rules) {
if ($scope.onpage.rules[i].label == $scope.viewrulelabel) {
ret[i] = $scope.onpage.rules[i];
}
}
$scope.onpage = { 'rules': ret, 'rulelabels': $scope.onpage.rulelabels };
}
fnRules($rootScope._pages);
fnChart($rootScope._history, $scope.viewrulelabel);
}
}
}
$scope.$on('refreshonpagelist', function(e) {
fnIndexList();
});
$scope.fnStar = function(curpage){
curpage.properties.star = (curpage.properties.star == 1)?0:1;;
$http.post('/rest/onpage/page.save', {
websiteid: h.siteId(),
pageid: curpage._id.$oid,
fields: { 'properties.star': curpage.properties.star }
});
//$http.post("/rest/rank-tracker/edit", angular.extend({ 'meta.star': !ranking.meta.star, fields: ['meta.star'] }, fnSingle.getParams(ranking))).then(function successCallback(res) { $scope.getKeywords(); })
}
// get list
var fnIndexList = function() {
$scope.page.table.fnOrder('properties.opi', true)
//allways get rule list
$http.get('/rest/onpage/rules', { 'params': { 'websiteid': h.siteId(), 'lang': $scope.user.site.locale.lang } }).then(function successCallback(res) {
angular.forEach(res.data.rules, function(v, k) {
v.enabled = !v.disabled;
})
$scope.onpage = res.data;
// get pages and historu of cache
if ($rootScope._pages && $rootScope.onpagestatus.data.status == 'done') {
$scope.pages = angular.copy($rootScope._pages);
$scope.history = angular.copy($rootScope._history);
fnInit();
} else {
$http.get('/rest/onpage/list', {
params: {
websiteid: h.siteId(),
}
}).then(function successCallback(res) {
$rootScope._pages = res.data.data;
$rootScope._history = res.data.history;
$scope.pages = angular.copy($rootScope._pages);
$scope.history = angular.copy($rootScope._history);
fnInit();
});
}
});
}
if ($scope.custominit) {
$scope.viewrule = $scope.custominit.viewrule;
$scope.viewrulelabel = $scope.custominit.viewrulelabel;
};
fnIndexList();
$scope.fnScanByUrl = function() {
$scope.fnGoto('/rest/onpage/page.create?websiteid=' + h.siteId() + '&url=' + $scope.page.newurl);
}
}
]);
app.controller('onpagescan', ['$scope', '$http', '$rootScope', '$filter', '$location', 'h',
function($scope, $http, $rootScope, $filter, $location, h) {
//page_id
var _pages;
$scope.page = { 'pending': true, 'overlay': {}, 'charts': {}, 'q': '' };
// ?tab=aicontent opent meteen dat tabblad (link vanaf de AI-pagina)
$scope.initialTab = $location.search().tab || 'score';
// AI-signalen (beta): uit de verse scan (met de hele tekst) of anders uit de opgeslagen pagina
$scope.fnAi = function() {
return ($scope.scan && $scope.scan.aicontent) || ($scope.pageinfo && $scope.pageinfo.aicontent) || null;
}
$scope.fnAiReasons = function(reasons) {
return (reasons || []).map(function(k) {
return $filter('translate')('onpage.aicontent.sig_' + k + '_title');
}).join(', ');
}
// filter op één patroon in de tekst, en de uitgeklapte uitleg (object: het tabblad zit in een child scope)
$scope.ai = { 'filter': null, 'info': false };
// patronen die per zin gemarkeerd worden; de rest (ritme, alineavorm, leesniveau) geldt voor de hele pagina
$scope.fnAiIsSentenceSignal = function(k) {
return ['phrases', 'buzzwords', 'transitions', 'em_dash', 'not_but', 'triplets'].indexOf(k) > -1;
}
$scope.fnAiLevelClass = function(level) {
return {
'high': 'bg-red-100 text-red-800',
'medium': 'bg-amber-100 text-amber-800',
'low': 'bg-green-100 text-green-800'
}[level] || 'bg-gray-100 text-gray-600';
}
var _cb = {
'redir_back_to_index': function() {
$location.path('/onpage-seo/all/all');
},
}
$scope.fnPctChange = function(o, n) {
if (!o) {
return '';
}
if (n == 0) {
return '0%';
}
var pct = ((n - o) / o) * 100;
var npct = $filter('number')(pct, 2);
if (pct < 0) {
return '' + npct + '%';
}
return '+ ' + npct + '%';
}
$scope.fnScoreToColour = function(o){
if (!o) {
return '';
}
if (o < 50) {
return ''+o+'%';
}
if (o < 75) {
return ''+o+'%';
}
return ''+o+'%';
}
/* perform scan*/
var fn = {
info: function(cb) {
/* get ruleset */
$http.get('/rest/onpage/rules', { 'params': { 'websiteid': h.siteId(), 'lang': $scope.user.site.locale.lang } }).then(function successCallback(res) {
angular.forEach(res.data.rules, function(v, k) {
v.enabled = !v.disabled;
})
$scope.onpage = res.data;
$http.get('/rest/onpage/page.info', {
params: {
websiteid: h.siteId(),
pageid: $scope.pageid,
}
}).then(function successCallback(res) {
$scope.pageinfo = res.data.data;
cb();
});
});
},
scan: function(cb) {
$http.get('/rest/onpage/page.scan', {
params: {
websiteid: h.siteId(),
pageid: $scope.pageid,
}
}).then(function successCallback(res) {
$scope.page.pending = false;
$scope.scan = res.data.data;
$scope.scan.avg = { 'all': 0, 'technical': 0, 'content': 0, 'mobile': 0, 'indexability': 0, 'social': 0 }
var l = $scope.scan.history.length;
angular.forEach($scope.scan.history, function(value, key) {
$scope.scan.avg['all'] += value.all / l
$scope.scan.avg['technical'] += value.technical / l
$scope.scan.avg['content'] += value.content / l
$scope.scan.avg['mobile'] += value.mobile / l
$scope.scan.avg['indexability'] += value.indexability / l
$scope.scan.avg['social'] += value.social / l
});
$scope.fnDateChart('history', 'all', { data: res.data.data.history }, false)
cb();
});
},
stats: function(rules) {
var stats = {};
for (var i in $scope.onpage['rules']) {
var rule = $scope.onpage['rules'][i];
if (rules[i]) {
stats[rule.label] = stats[rule.label] + 1 || 1;
}
}
},
searchconsole(cb) {
var addslash = true;
$http.get('/rest/searchconsole/report', {
params: { websiteid: h.siteId(), 'opportunity':'true','dimension': 'query', 'start': '90DAYSAGO', 'end': 'TODAY', 'metrics': 'clicks,ctr,position,impressions', 'report': 'searchAnalytics', 'addslash': addslash, 'filterdimension': "page", 'filterexpression': $scope.pageinfo.serps.url, 'filteroperator': "equals", limit: 6000 }
}).then(function successCallback(res) {
if (res.data.data.length > 0) {
$scope.searchconsolequeries = res.data;
} else {
addslash = false;
$http.get('/rest/searchconsole/report', {
params: { websiteid: h.siteId(), 'opportunity':'true','dimension': 'query', 'start': '90DAYSAGO', 'end': 'TODAY', 'metrics': 'clicks,ctr,position,impressions', 'report': 'searchAnalytics', 'addslash': addslash, 'filterdimension': "page", 'filterexpression': $scope.pageinfo.serps.url, 'filteroperator': "equals", limit: 6000 }
}).then(function successCallback(res) {
$scope.searchconsolequeries = res.data;
})
}
$http.get('/rest/searchconsole/report', {
params: { websiteid: h.siteId(), 'dimension': 'date', 'start': '90DAYSAGO', 'end': 'TODAY', 'metrics': 'clicks,ctr,position,impressions', 'report': 'searchAnalytics', 'addslash': addslash, 'filterdimension': "page", 'filterexpression': $scope.pageinfo.serps.url, 'filteroperator': "equals" }
}).then(function successCallback(res) {
if (res.data.data.length > 0) {
$scope.searchconsoledates = res.data;
$scope.fnDateChart('searchconsole', 'clicks', $scope.searchconsoledates, true);
}
})
})
},
inlinks() {
// make summary for inlinks ... shoudl I do this in rest ??
var r = {}
var data = []
for (var i in $scope.pageinfo.inlinks.links) {
var t = $scope.pageinfo.inlinks.links[i];
r[t.anchor] = r[t.anchor] + 1 || 1;
}
for (var i in r) {
data.push({ 'anchor': i, 'n': r[i] });
}
fnPieChart('inlinks', 'anchor', 'n', data)
},
interceptions() {
// make summary for interceptions ... shoudl I do this in rest ??
var r = {}
var data = []
for (var i in $scope.scan.scan['response_interceptions']) {
var t = $scope.scan.scan['response_interceptions'][i];
t['content-length'] = parseInt((parseInt(t['content-length']) > 0) ? t['content-length'] : 0);
r[t['content-type']] = r[t['content-type']] + t['content-length'] || t['content-length'];
}
for (var i in r) {
data.push({ 'content-type': i, 'total': r[i] });
}
fnPieChart('interceptions', 'content-type', 'total', data)
},
analytics() {
$http.get('/rest/analytics4/report', {
params: { websiteid: h.siteId(), 'dimension': 'date', 'start': '30DAYSAGO', 'end': 'TODAY', 'metrics': 'sessions,totalUsers,screenPageViews,screenPageViewsPerSession,averageSessionDuration,newUsers,bounceRate,eventCount,eventCountPerUser', 'path': $scope.pageinfo.url, 'sort': 'date', 'sortorder': "ASCENDING" }
}).then(function successCallback(res) {
$scope.analyticsdates = res.data;
$scope.fnDateChart('analytics', 'sessions', $scope.analyticsdates, true);
})
$http.get('/rest/analytics4/report', {
params: { websiteid: h.siteId(), 'dimension': 'sessionDefaultChannelGrouping', 'start': '30DAYSAGO', 'end': 'TODAY', 'metrics': 'sessions,totalUsers,screenPageViews,screenPageViewsPerSession,averageSessionDuration,newUsers,bounceRate,eventCount,eventCountPerUser', 'path': $scope.pageinfo.url, 'sort': 'users', 'sortorder': "DESCENDING" }
}).then(function successCallback(res) {
$scope.analyticschannels = res.data;
})
},
}
fn.info(
function() {
fn.stats($scope.pageinfo.rules);
fn.analytics();
fn.searchconsole();
fn.inlinks();
}
);
fn.scan(
function() {
fn.interceptions();
}
);
var fnPieChart = function(name, field, value, data) {
$scope.page.charts[name] = {
data: data.slice(0, 9),
type: 'pie',
theme: 'light',
marginLeft: 0,
marginRight: 0,
marginTop: 10,
fontFamily: 'Roboto',
categoryField: field,
pathToImages: 'vendor/amchart/images/',
"precision": 0,
"decimalSeparator": ",",
"thousandsSeparator": ".",
legend: {
enabled: true,
position: 'right',
autoMargins: false,
marginRight: 100,
},
titleField: field,
valueField: value,
labelRadius: 5,
radius: '42%',
innerRadius: '60%',
labelText: '[[title]]',
}
}
$scope.fnDateChart = function(name, field, data, reverse) {
data.field = field;
data.reverse = reverse
var opts = {
'searchconsole': { 'clicks': true, 'ctr': true, 'position': false, 'impressions': true },
'analytics': { 'users': true, 'pageviews': true, 'pageviewsPerSession': false, 'avgSessionDuration': true, 'percentNewSessions': true, 'bounceRate': true, 'goalCompletionsAll': true, 'goalValueAll': true },
'history': { 'all': false }
};
var dateformats = { 'searchconsole': 'YYYY-MM-DD', 'analytics': 'YYYYMMDD', 'history': 'YYYY-MM-DD' };
var datefields = { 'searchconsole': 'date', 'analytics': 'date', 'history': 'date' };
var colors = { 'searchconsole': '#007bff', 'analytics': '#28a745', 'history': '#28a745' };
datareverse = opts[name][field];
var c = {
data: data.data,
type: 'serial',
theme: 'light',
marginLeft: 0,
marginRight: 0,
marginTop: 10,
fontFamily: 'Roboto',
categoryField: datefields[name],
rotate: false,
dataDateFormat: dateformats[name],
categoryAxis: {
gridPosition: "start",
parseDates: true,
gridThickness: 0
},
valueAxes: [{
position: "left",
axisAlpha: 0,
labelsEnabled: true,
dashLength: 4,
color: '#dedede',
}],
graphs: [{
balloonText: "
[[value]]",
"bullet": "round",
"bulletBorderAlpha": 1,
"bulletColor": "#FFFFFF",
"useLineColorForBulletBorder": true,
"fillAlphas": 0,
"lineThickness": 2,
"lineAlpha": 1,
"bulletSize": 7,
"title": "Expenses",
"valueField": field,
lineColor: colors[name],
}],
}
if (name == 'history') {
c.autoMargins = false;
c.marginLeft = 0;
c.marginRight = 0;
c.marginTop = 0;
c.marginBottom = 0;
c.valueAxes[0].gridAlpha = 0;
c.valueAxes[0].axisAlpha = 0;
c.valueAxes[0].color = '#ffffff';
c.valueAxes[0].dashLength = 0;
c.categoryAxis.gridAlpha = 0;
c.categoryAxis.axisAlpha = 0;
}
$scope.page.charts[name] = c;
}
$scope.fnHide = function(type) {
angular.element('#' + type).addClass('d-none');
}
$scope.fnExpose = function(bounds, type) {
angular.element('#' + type).removeClass('d-none');
if (bounds.h > 0) {
var overlay = {}
if (type == 'mobile') {
var width = document.getElementById(type).clientWidth;
var scale = 360 / width;
} else {
var width = document.getElementById(type).clientWidth;
var scale = 1366 / width;
}
var scaledbounds = { l: bounds.l / scale, t: bounds.t / scale, w: bounds.w / scale, h: bounds.h / scale };
overlay.c = { 'top': scaledbounds.t, 'left': scaledbounds.l, 'width': scaledbounds.w, 'height': scaledbounds.h }
overlay.tl = { 'width': scaledbounds.l + scaledbounds.w / 2, 'height': scaledbounds.t }
overlay.tr = { 'width': width - (scaledbounds.l + scaledbounds.w / 2), 'height': scaledbounds.t }
overlay.bl = { 'width': scaledbounds.l + scaledbounds.w / 2, 'top': scaledbounds.t + scaledbounds.h }
overlay.br = { 'width': width - (scaledbounds.l + scaledbounds.w / 2), 'top': scaledbounds.t + scaledbounds.h }
overlay.lct = { 'width': scaledbounds.l, 'top': scaledbounds.t, 'height': scaledbounds.h / 2 }
overlay.lcb = { 'width': scaledbounds.l, 'top': scaledbounds.t + scaledbounds.h / 2, 'height': scaledbounds.h / 2 }
overlay.rct = { 'width': width - (scaledbounds.l + scaledbounds.w), 'top': scaledbounds.t, 'height': scaledbounds.h / 2 }
overlay.rcb = { 'width': width - (scaledbounds.l + scaledbounds.w), 'top': scaledbounds.t + scaledbounds.h / 2, 'height': scaledbounds.h / 2 }
$scope.page.overlay[type] = overlay;
var scrolltop = ((bounds.t / scale) - 100 < 0) ? 0 : (bounds.t / scale - 100);
angular.element('#' + type).stop(true, true).animate({ 'scrollTop': scrolltop + "px" }, 400)
}
}
$scope.fnSaveRules = function() {
var disabled_rules = Object.keys($scope.onpage.rules).reduce(function(acc,cur){
if(!$scope.onpage.rules[cur].enabled){
acc[cur] = true;
}
return acc;
},{});
$http.post('/rest/onpage/rules.disabled.set', {
'websiteid': h.siteId(),
'disabled_rules': disabled_rules,
}).then(function successCallback(res) {
_cb[res.data.cb]();
});
}
$scope.fnSavePage = function() {
$http.post('/rest/onpage/page.save', {
websiteid: h.siteId(),
pageid: $scope.pageid,
fields: { 'q': $scope.pageinfo.q, 'qneg': $scope.pageinfo.qneg }
}).then(function successCallback(res) {
_cb[res.data.cb]();
});
}
$scope.fnDeletePage = function() {
$http.post('/rest/onpage/page.delete', {
websiteid: h.siteId(),
pageid: $scope.pageid,
}).then(function successCallback(res) {
_cb[res.data.cb]();
});
}
}
]);
app.controller('speedtest', ['$scope', '$http', '$rootScope', '$filter', '$window', 'h',
function($scope, $http, $rootScope, $filter, $window, h) {
var TARGET_TTFB = 64;
$scope.speedtestpage = { 'state': 'init', 'url': $scope.pageinfo.url };
$scope.t = { 'ttfb': 'TTFB', 'dns': 'DNS', 'dominteractive': 'DOM Interactive', 'domcontentloaded': 'DOM Content Loaded', 'onload': 'On Load', 'firstcontentfulfulpaint': 'FCP', 'largestcontentfulpaint': 'LCP', 'visualReady': 'visual Ready' };
// no need to bind everything
$scope.do = function(o, f) {
speedtest[f]();
}
$scope.getColor = function(v) {
return (v < 50) ? '#dc3545' : (v < 75) ? '#FF9800' : '#28a745';
}
//main
var speedtest = {
init: function() {
if (!$scope.speedtest) {
$scope.speedtestpage.state = 'pending';
$http.get('https://robinssl.seoserver.nl/speedtest/scan.php', {
params: {
'url': $scope.speedtestpage.url,
}
}).then(function successCallback(response) {
if (response.data.error) {
$scope.speedtestpage.state = 'error';
} else {
$scope.speedtestpage.state = 'done';
$scope.speedtest = response.data;
$scope.speedtest.timeline = {};
$scope.speedtest.date = new Date();
speedtest._totaltimeTTFB();
speedtest._firstPage();
speedtest._createResourceTimelineFromEventInterest('BlockingJavaScript')
speedtest._createResourceTimelineFromEventInterest('nonBlockingJavaScript')
speedtest._createResourceTimelineFromEventInterest('BlockingCss')
speedtest._createResourceTimelineFromResourceInterest('ImageNotLazy')
speedtest._createResourceTimelineFromResourceInterest('IncorrectImageScale')
speedtest._createResourceTimelineFromResourceInterest('BlockingFont')
speedtest._createResourceTimelineFromResourceInterest('NotCacheAble')
speedtest._createResourceTimelineFromResourceInterest('NotCompressed')
speedtest._createResourceTimelineFromResourceInterest('NotHttp2')
speedtest._createFilmstrip()
// estimated savings
$scope.speedtest.timeline['NotCompressed'].est_saving *= .15 /*15% faster*/
$scope.speedtest.timeline['NotHttp2'].est_saving *= .36 /*36% faster*/
var scores = {
'firstcontentfulfulpaint': score.computeLogNormalScore(speedtest._getTimingByKey('firstcontentfulfulpaint'), 600, 2200),
'largestcontentfulpaint': score.computeLogNormalScore(speedtest._getTimingByKey('largestcontentfulpaint'), 800, 2600),
'visualReady': score.computeLogNormalScore(speedtest._getTimingByKey('visualReady'), 3000, 4000),
'totaltimeTTFB': score.computeLogNormalScore($scope.speedtest.timeline['totaltimeTTFB'].est_saving, 400, 600),
'NotHttp2': score.computeLogNormalScore($scope.speedtest.timeline['NotHttp2'].est_saving, 400, 600),
'firstPage': score.computeLogNormalScore($scope.speedtest.timeline['firstPage'].est_saving, 80, 160),
'BlockingJavaScript': score.computeLogNormalScore($scope.speedtest.timeline['BlockingJavaScript'].est_saving, 50, 100),
'nonBlockingJavaScript': score.computeLogNormalScore($scope.speedtest.timeline['nonBlockingJavaScript'].est_saving, 1000, 2000),
'BlockingCss': score.computeLogNormalScore($scope.speedtest.timeline['BlockingCss'].est_saving, 60, 120),
'BlockingFont': score.computeLogNormalScore($scope.speedtest.timeline['BlockingFont'].est_saving, 50, 100),
'ImageNotLazy': score.computeLogNormalScore($scope.speedtest.timeline['ImageNotLazy'].est_saving, 150, 300),
'IncorrectImageScale': score.computeLogNormalScore($scope.speedtest.timeline['ImageNotLazy'].est_saving, 150, 300),
'NotCompressed': score.computeLogNormalScore($scope.speedtest.timeline['NotCompressed'].est_saving, 150, 300),
'NotCacheAble': score.computeLogNormalScore($scope.speedtest.timeline['NotCacheAble'].est_saving, 150, 300),
}
$scope.scores = {
'total': Math.round((scores.firstPage + scores.firstcontentfulfulpaint + scores.largestcontentfulpaint + scores.visualReady + scores.NotHttp2 + scores.BlockingJavaScript + scores.BlockingCss + scores.BlockingFont) / 8),
'server': Math.round((scores.firstPage) / 1),
'paint': Math.round((scores.firstcontentfulfulpaint + scores.largestcontentfulpaint + scores.BlockingJavaScript + scores.BlockingCss + scores.BlockingFont) / 5),
'webflow': Math.round((scores.ImageNotLazy + scores.totaltimeTTFB + scores.NotCacheAble + scores.NotCompressed + scores.NotHttp2 + scores.nonBlockingJavaScript) / 6),
'part': scores
}
$scope.speedtestpage.state = 'done';
}
});
}
},
_getTimingByKey: function(key) {
return ($scope.speedtest.timing.filter(function(a) { return (a['name'] === key) })[0] || { 'ts': 0 }).ts;
},
_createFilmstrip: function() {
var n = document.querySelector('#filmstrip').clientWidth / 55;
$scope.filmstrip = [];
// fake ts ergens in midden
for (var i = 0; i < n; i++) {
var y = ($scope.speedtest.ts.last / n) * i;
var filmstrip = $scope.speedtest.filmstrip.reduce(function image(acc, cur) {
return ((Math.abs(cur.ts - y) < Math.abs(acc.ts - y)) && cur.ts < y) ? cur : acc;
})
$scope.filmstrip.push(filmstrip.args.snapshot);
}
},
_firstPage: function() {
var firstPage = $scope.speedtest.resources[0]
firstPage.timing = firstPage.timing || { receiveHeadersEnd: TARGET_TTFB }
var saving = (firstPage.timing.receiveHeadersEnd > TARGET_TTFB) ? firstPage.timing.receiveHeadersEnd - TARGET_TTFB : 0
var timeline = {}
timeline[firstPage.url] = { 'resources': [firstPage], 'totaltime': firstPage.timing.receiveHeadersEnd }
$scope.speedtest.timeline['firstPage'] = { 'timeline': timeline, 'est_saving': saving, 'totaltime': firstPage.duration, 'ttfb': speedtest._getTimingByKey('ttfb') }
},
_totaltimeTTFB: function() {
// calc ttfb if larger then good ttfp, deep copy resouces and overwrite duration
var resourceclone = JSON.parse(JSON.stringify($scope.speedtest.resources));
var cutoff = speedtest._getTimingByKey('visualReady');
var ResourcesOfInterestUrls = resourceclone.reduce(function(acc, resource) {
resource.timing = resource.timing || { receiveHeadersEnd: TARGET_TTFB }
if (resource.timing.receiveHeadersEnd > TARGET_TTFB && resource.endTime < cutoff) {
resource.endTime = resource.startTime + resource.timing.receiveHeadersEnd - TARGET_TTFB
resource.duration = resource.timing.receiveHeadersEnd - TARGET_TTFB
acc.push(resource)
}
return acc;
}, []);
var timeline = ResourcesOfInterestUrls.reduce(function(a, b) {
a[b.url] = a[b.url] || { 'resources': [], 'totaltime': 0 }
return a;
}, {})
timeline = h.__mergeResources(timeline, ResourcesOfInterestUrls);
$scope.speedtest.timeline['totaltimeTTFB'] = h.__createTimeLine(timeline);
},
_createResourceTimelineFromResourceInterest: function(Interest) {
var ResourcesOfInterestUrls = $scope.speedtest.resources.filter(function(resource) {
return resource.interest.includes(Interest)
});
// create timeline from Resources
var timeline = ResourcesOfInterestUrls.reduce(function(a, b) {
a[b.url] = a[b.url] || { 'resources': [], 'totaltime': 0 }
return a;
}, {})
timeline = h.__mergeResources(timeline, ResourcesOfInterestUrls);
$scope.speedtest.timeline[Interest] = h.__createTimeLine(timeline);
},
_createResourceTimelineFromEventInterest: function(EventInterest) {
var allEvents = $scope.speedtest.eventsOfInterest.filter(function(event) {
return event.interest[0] == EventInterest
});
// creat timeline from events
var timeline = allEvents.reduce(function(a, b) {
a[b.url] = a[b.url] || { 'resources': [], 'totaltime': 0 }
a[b.url].resources.push(b);
return a;
}, {})
timeline = h.__mergeResources2(timeline, $scope.speedtest.resources);
$scope.speedtest.timeline[EventInterest] = h.__createTimeLine(timeline)
}
}
//helpers
var h = {
__mergeResources: function(timeline, allResources) {
// merge resources to timeline, url must match and type not html
angular.forEach(timeline, function(v, k) {
var myResources = Object.values(allResources).filter(function(e) {
return e.url == k;
//return e.url == k && e.mimeType != 'text/html';
})
timeline[k].resources = timeline[k].resources.concat(myResources);
timeline[k].totaltime = timeline[k].resources.reduce(function(acc, cur) { return acc + cur.duration }, 0);
});
return timeline;
},
__mergeResources2: function(timeline, allResources) {
// merge resources to timeline, url must match and type not html
angular.forEach(timeline, function(v, k) {
var myResources = Object.values(allResources).filter(function(e) {
//return e.url == k;
return e.url == k && e.mimeType != 'text/html';
})
timeline[k].resources = timeline[k].resources.concat(myResources);
timeline[k].totaltime = timeline[k].resources.reduce(function(acc, cur) { return acc + cur.duration }, 0);
});
return timeline;
},
__mergeTiming: function(aTiming) {
// sort timing (needer for merge)
aTiming = aTiming.sort(function(a, b) { return a[0] - b[0] })
// merge overlapping time
var mergedtime = aTiming.reduce(function(combined, next) {
if (!combined.length || combined[combined.length - 1][1] < next[0]) combined.push(next);
else {
var prev = combined.pop();
combined.push([prev[0], Math.max(prev[1], next[1])]);
}
return combined;
}, []);
// calculate total time
var totaltime = mergedtime.reduce(function(a, b) {
return (a + b[1] - b[0]);
}, 0);
return totaltime;
},
__createTimeLine(timeline) {
// fetch array of timings [[1,4],[2,3]] etc
var aTiming = [];
angular.forEach(timeline, function(v, url) {
var tmp = v.resources.reduce(function(a, b) {
a.push([b.startTime, b.endTime]);
return a;
}, []);
aTiming = aTiming.concat(tmp);
});
var est_saving = h.__mergeTiming(aTiming);
return { 'timeline': timeline, 'est_saving': est_saving, 'n_timeranges': aTiming.length }
},
}
//score
var score = {
computeLogNormalScore: function(measuredValue, diminishingReturnsValue, medianValue) {
var distribution = this.getLogNormalDistribution(
medianValue,
diminishingReturnsValue
);
let score = distribution.computeComplementaryPercentile(measuredValue);
score = Math.min(1, score);
score = Math.max(0, score);
return Math.round(score * 100);
},
erf: function(x) {
var sign = Math.sign(x);
x = Math.abs(x);
var a1 = 0.254829592;
var a2 = -0.284496736;
var a3 = 1.421413741;
var a4 = -1.453152027;
var a5 = 1.061405429;
var p = 0.3275911;
var t = 1 / (1 + p * x);
var y = t * (a1 + t * (a2 + t * (a3 + t * (a4 + t * a5))));
return sign * (1 - y * Math.exp(-x * x));
},
getLogNormalDistribution: function(median, falloff) {
var location = Math.log(median);
// The "falloff" value specified the location of the smaller of the positive
// roots of the third derivative of the log-normal CDF. Calculate the shape
// parameter in terms of that value and the median.
var logRatio = Math.log(falloff / median);
var shape = Math.sqrt(1 - 3 * logRatio - Math.sqrt((logRatio - 3) * (logRatio - 3) - 8)) / 2;
return {
computeComplementaryPercentile(x) {
var standardizedX = (Math.log(x) - location) / (Math.SQRT2 * shape);
return (1 - score.erf(standardizedX)) / 2;
},
};
}
}
//stupid resize
angular.element($window).bind('resize', function() {
speedtest._createFilmstrip();
$scope.$apply()
});
}
]);
app.controller('planning', ['$scope', '$timeout', '$http', '$rootScope', '$filter', 'h',
function($scope, $timeout, $http, $rootScope, $filter, h) {
$scope.curtemplate = {};
$scope.categorys = {};
var aTemplateIds = [];
// do
$scope.do = function(obj, method, param) {
if (obj == 'users') {
return users[method](param);
} else if (obj == 'taken') {
return taken[method](param);
} else if (obj == 'taak') {
return taak[method](param);
} else if (obj == 'fnTemplates') {
return fnTemplates[method](param);
}
}
var users = {
userList: function() {
$http.get('/rest/user/reseller.site.list', {
params: {
websiteid: h.siteId(),
}
}).then(function successCallback(res) {
$scope.users = res.data;
});
},
matchUser: function(UserId) {
for (var i in $scope.users) {
if ($scope.users[i]._id.$oid == UserId) {
return $scope.users[i].name;
}
}
return '';
}
}
var taken = {
'data': { taken: [] },
init: function() {
$scope.datestring = ($rootScope.reportdates || { datestring: false }).datestring || new Date().toLocaleString('default', { year: 'numeric', 'month': 'long' })
//tming could be wrong ... callback?
users.userList();
taken.getTaken();
},
setDateString: function(s) {
$scope.datestring = s;
taken.getStats();
},
fnReset: function(mtype) {
console.log(mtype);
var aantal = (mtype.type == 'taak') ? { 'aantal': 1 } : {};
$scope.curtaak = angular.extend({ 'type': "taak", 'repeat': "", 'status': 'open', 'datum': new Date(), 'workflow': [{ 'uren': 0, 'user_id': $rootScope.user._id.$oid + "" }] }, mtype, aantal)
console.log($scope.curtaak);
/* $scope.curtaak = { 'aantal': 1, 'type': "taak", 'repeat': "", 'status': 'open', 'datum': new Date(), 'workflow': [{ 'uren': 0, 'user_id': $rootScope.user._id.$oid + "" }] };
if (mtype == 'workflow') {
$scope.curtaak = { 'type': "workflow", 'repeat': "", 'status': 'open', 'datum': new Date(), 'workflow': [{ 'uren': 0, 'user_id': $rootScope.user._id.$oid + "" }] };
} else {
$scope.curtaak = { 'aantal': 1, 'type': "taak", 'repeat': "", 'status': 'open', 'datum': new Date(), 'workflow': [{ 'uren': 0, 'user_id': $rootScope.user._id.$oid + "" }] };
}*/
},
getTaken: function() {
$http.get("/rest/planning/planning.list", {
params: {
websiteid: h.siteId(),
}
}).then(function successCallback(res) {
taken.data = res.data;
$scope.taken = taken.data.taken;
fnTemplates.init();
});
},
getStats: function() {
console.log('GET STATS');
var categories = {};
var overzicht = {};
var urenPerUser = {};
//loop planning
taken.data.taken.forEach(function(planning, k) {
//date conversion
planning.datum = new Date(planning.datum);
planning.datestring = planning.datum.toLocaleString('default', { year: 'numeric', 'month': 'long' })
//overichten aanmaken
overzicht[planning.datestring] = overzicht[planning.datestring] || { 'workflow': { 'open': 0, 'done': 0, 'active': 0 }, 'taak': { 'open': 0, 'done': 0 } }
// workflow
if (planning.type == 'workflow') {
planning.workflow.forEach(function(taak) {
// opverzicht[open|done] optellen
//er is een category
if (planning.template_id && aTemplateIds.indexOf(planning.template_id['$oid']) > -1) {
categories[planning.category] = { 'order': 1, 'name': planning.category, 'type': 'workflow' };
overzicht[planning.datestring][planning.category] = overzicht[planning.datestring][planning.category] || { 'open': 0, 'done': 0, 'active': 0 }
overzicht[planning.datestring][planning.category][planning.status] += (taak.uren * 1)
} else {
categories['workflow'] = { 'order': 2, 'name': 'workflow', 'type': 'workflow', 'translate': true };
overzicht[planning.datestring].workflow[planning.status] += (taak.uren * 1)
console.log(taak.uren);
console.log(planning.status);
}
if (planning.datestring == $scope.datestring) {
// user per user
urenPerUser[taak.user_id] = urenPerUser[taak.user_id] || 0
urenPerUser[taak.user_id] += (taak.status == 'done') ? taak.uren * 1 : 0;
}
});
// taak
} else {
// opverzicht[open|done] optellen
if (planning.template_id && aTemplateIds.indexOf(planning.template_id['$oid']) > -1) {
categories[planning.category] = { 'order': 1, 'name': planning.category, 'type': 'taak', 'translate': false };
overzicht[planning.datestring][planning.category] = overzicht[planning.datestring][planning.category] || { 'open': 0, 'done': 0 }
overzicht[planning.datestring][planning.category][planning.status] += (planning.aantal * 1)
} else {
categories['taak'] = { 'order': 2, 'name': 'taak', 'type': 'taak', 'translate': true };
overzicht[planning.datestring].taak[planning.status] += (planning.aantal * 1)
}
}
});
$scope.categories = Object.values(categories);
$scope.overzicht = overzicht;
charts.piechart(urenPerUser);
}
}
var fnTemplates = {
init: function() {
fnTemplates.list();
},
list: function() {
$http.get('/rest/planning/template.list', {
params: {
websiteid: h.siteId(),
}
}).then(function successCallback(res) {
$scope.taaktemplates = res.data;
aTemplateIds = res.data.map(function(c) { return c.template_id['$oid'] });
taken.getStats();
});
},
pre_edit: function() {
$scope.fnModal('planning/modal.pre_edit');
},
create: function() {
$http.post('/rest/planning/template.save', { 'template': $scope.curtemplate }).then(function successCallback(res) {
$scope.fnCloseModal();
fnTemplates.list();
});
},
save: function(template) {
$http.post('/rest/planning/template.save', { websiteid: h.siteId(), 'template': template }).then(function successCallback(res) {
$scope.fnCloseModal();
fnTemplates.list();
});
},
delete: function(template) {
$http.post('/rest/planning/template.delete', template).then(function successCallback(res) {
$scope.fnCloseModal();
fnTemplates.list();
});
},
}
var taak = {
addWorkFlow: function() {
$scope.curtaak.workflow.push({ user_id: $rootScope.user._id.$oid + "" });
},
removeWorkFlow: function(index) {
$scope.curtaak.workflow.splice(index, 1);
},
useTemplate: function(curtaak) {
$scope.curtaak = curtaak;
},
addCurtaak: function(taak) {
$http.post('/rest/planning/planning.add', angular.extend($scope.curtaak, { websiteid: h.siteId(), })).then(function successCallback(res) {
taken.getTaken();
});
},
updateCurtaak: function(taak) {
$http.post('/rest/planning/planning.update', angular.extend($scope.curtaak, { websiteid: h.siteId(), })).then(function successCallback(res) {
taken.getTaken();
});
},
editTaakStatus: function(taak) {
taak.status = (taak.status == 'done') ? 'open' : 'done';
$http.post('/rest/planning/planning.update', angular.extend(taak, { websiteid: h.siteId(), })).then(function successCallback(res) {
taken.getTaken();
});
},
editTaak: function(taak) {
$scope.curtaak = taak;
$scope.fnModal('planning/modal.' + taak['type']);
},
setVoortgang: function([taak, index]) {
$http.post('/rest/planning/planning.progress', { 'taak': taak, 'index': index, websiteid: h.siteId() }).then(function successCallback(res) {
taken.getTaken();
});
},
removeCurtaak: function(taak) {
$http.post('/rest/planning/planning.delete', angular.extend($scope.curtaak, { websiteid: h.siteId(), })).then(function successCallback(res) {
taken.getTaken();
});
},
}
var charts = {
piechart: function(urenPerUser) {
var colors = h.colors();
// pie data zetten
var piedata = Object.keys(urenPerUser).reduce(function(acc, cur, idx) {
acc.push({ "color": colors[idx], label: users.matchUser(cur), data: urenPerUser[cur] })
return acc;
}, []);
$scope.piechart = {
data: piedata,
type: 'pie',
marginLeft: 0,
marginRight: 0,
marginTop: 0,
categoryField: 'label',
colorField: 'color',
"precision": 0,
"decimalSeparator": ",",
"thousandsSeparator": ".",
titleField: 'label',
valueField: 'data',
labelRadius: 5,
radius: '42%',
innerRadius: '60%',
labelText: '[[title]]',
}
}
}
taken.init();
}
]);
app.controller('reseller', ['$scope', '$timeout', '$http', '$rootScope', '$location', '$anchorScroll', '$filter', 'h',
function($scope, $timeout, $http, $rootScope, $location, $anchorScroll, $filter, h) {
$scope.page = {
newadmin: {},
resetpaymentwarning: false,
testemail: $rootScope.user.email
};
$scope.adminroles = [
{ 'role': 2, 'name': 'administrator' },
{ 'role': 3, 'name': 'team member' },
]
$scope.roles = [
{ 'role': 2, 'name': 'white label admin' },
{ 'role': 3, 'name': 'administrator' },
//{ 'role': 4, 'name': 'site owner' },
{ 'role': 5, 'name': 'team member' },
{ 'role': 6, 'name': 'guest' },
]
$scope.do = function(obj, method, param) {
if (obj == 'fnReseller') {
fnReseller[method](param);
} else if (obj == 'fnPage') {
fnPage[method](param);
}
}
var fnReseller = {
// When a tool key is missing from config.tools[role], the checkbox looks
// off even if permission::show() defaults it on. Seed MCP and uptime so the matrix
// matches runtime (MCP on for roles < 6, uptime on for all) until explicitly set.
seedToolDefaults: function() {
if (!$scope.tools || !$scope.user.reseller || !$scope.user.reseller.config) {
return;
}
if (!$scope.user.reseller.config.tools) {
$scope.user.reseller.config.tools = {};
}
angular.forEach($scope.roles, function (role) {
if (!$scope.user.reseller.config.tools[role.role]) {
$scope.user.reseller.config.tools[role.role] = {};
}
if (typeof $scope.user.reseller.config.tools[role.role].mcp === 'undefined') {
$scope.user.reseller.config.tools[role.role].mcp = (role.role < 6);
}
if (typeof $scope.user.reseller.config.tools[role.role].uptime === 'undefined') {
$scope.user.reseller.config.tools[role.role].uptime = true;
}
});
},
get: function() {
$http.get('/rest/reseller/reseller.get.fromuser').then(function successCallback(res) {
if (res.data._id) {
$scope.page.tab = 'edit';
$scope.user.reseller = res.data;
fnReseller.seedToolDefaults();
} else {
$scope.page.tab = 'intro';
}
});
$http.get('/rest/locale/language.list').then(function successCallback(res) {
$scope.languages = res.data;
});
$http.get('/rest/locale/country.list').then(function successCallback(res) {
$scope.countries = res.data;
});
$http.get('/rest/global/tools.list').then(function successCallback(res) {
$scope.tools = res.data;
fnReseller.seedToolDefaults();
});
},
'add': function() {
$http.post('/rest/reseller/reseller.admin.add',
$scope.page.newadmin
).then(function successCallback(res) {
fnReseller.get();
});
},
'testemail': function() {
$http.post('/rest/reseller/reseller.email.test', { 'email': $scope.page.testemail });
},
'delete': function(admin) {
$http.post('/rest/reseller/reseller.admin.delete',
admin
).then(function successCallback(res) {
fnReseller.get();
});
},
'update': function(reseller) {
$http.post('/rest/reseller/reseller.update',
$scope.user.reseller
).then(function successCallback(res) {
fnReseller.get();
});
},
'invoice':function(key){
$scope.fnGoto('https://robinssl.seoserver.nl/invoice/marketingtracer.php?reseller_id='+$scope.user.reseller._id.$oid+'&key='+key)
},
'resetbilling':function(){
$http.post('/rest/pay/reseller.reset',
$scope.user.reseller
).then(function successCallback(res) {
fnReseller.get();
});
}
}
fnReseller.get();
}
]);
app.controller('insights', ['$scope', '$timeout', '$http', '$rootScope', '$filter', 'h',
function($scope, $timeout, $http, $rootScope, $filter, h) {
var colorindex = 0;
$scope.page = {
tab: 'managers'
}
$scope.config = {
analytics: {
name: 'analytics visitors',
dataset: 'analytics',
metrickey: 'analytics',
metrics: ['sessions', 'users', 'pageviews', 'pageviewsPerSession', 'avgSessionDuration', 'percentNewSessions', 'bounceRate', 'goalValueAll', 'goalConversionRateAll', 'goalCompletionsAll']
},
adwords: {
name: 'adwords',
dataset: 'adwords',
metrickey: 'googlexads',
metrics: ['Clicks', 'Impressions', 'Cost', 'AverageCpc', 'Ctr', 'Conversions', 'CostPerConversion', 'ConversionRate', 'ConversionValue', 'roas']
},
searchconsole: {
name: 'searchconsole',
dataset: 'searchconsole',
metrickey: 'searchconsole',
metrics: ['clicks', 'position', 'impressions', 'ctr']
}
};
$scope.report = $scope.config[$scope.urlreport];
$scope.gloMetrics = h.metrics($scope.report.metrickey);
/* TEMP => RULES SHOULD BE FETCHED!!*/
var _rules = [{
'metric': 'avgSessionDuration',
'comp': 'lt',
'val': 70
}, {
'metric': 'bounceRate',
'comp': 'gt',
'val': 60
}, {
'metric': 'sessions',
'comp': 'drop',
'val': 30
}];
var rules = _rules.reduce((acc, item) => {
acc[item.metric] = item;
return acc;
}, {});
$scope.ruleopts = [{ v: 'gt', n: 'greater then (abs value)' },{ v: 'lt', n: 'less then (abs value)' },{ v: 'drop', n: 'drop (percentage)' },{ v: 'bump', n: 'increase (percentage)' }]
$scope.rules = _rules;
$scope.fnMatchesRule = function(metric, value, valueprev) {
var rule = $scope.rules.find(function(o) { return o.metric === metric });
if (rule) {
console.log(rule);
// less then abs value
if (rule.comp == 'lt' && value < rule.val) {
return 'bg-warning'
}
if (rule.comp == 'gt' && value > rule.val) {
return 'bg-warning'
}
if (rule.comp == 'drop' && (((valueprev - value) / value) * 100) > rule.val) {
return 'bg-warning'
}
return 'bg-white rulepass';
} else {
return 'bg-white norule';
}
}
// filter sites per user
var fnFilterUser = function(id) {
//var sites = angular.copy($scope.sites);
sites = $scope.sites.filter(function(v) {
for (var i in v.users) {
if (v.users[i].user_id.$oid == id.$oid) {
return true
}
}
return false;
});
return sites;
}
$scope.fnPctChange = function(o, n, reversesign) {
if (n == 0) {
return '0%';
}
var pct = ((n - o) / o) * 100;
var npct = $filter('number')(pct, 2);
if (reversesign) {
if (pct < 0) {
return '' + npct + '%';
}
return '+ ' + npct + '%';
}
if (pct < 0) {
return '' + npct + '%';
}
return '+ ' + npct + '%';
}
$http.get("/rest/insights/list", {
}).then(function successCallback(res) {
$scope.sites = res.data;
$scope.avg = fnCalcAvg($scope.sites);
// get chart data
$http.get('/rest/reseller/insights.get').then(function successCallback(res) {
$scope.insights = res.data[$scope.report.dataset];
fnLineChart('c1', $scope.insights, 'date', $scope.report.metrics[0]);
fnLineChart('c2', $scope.insights, 'date', $scope.report.metrics[1]);
});
// list users per reseller
$http.get('/rest/reseller/users.list').then(function successCallback(res) {
$scope.users = res.data;
for (var i in $scope.users) {
$scope.users[i]['avg'] = fnCalcAvg(fnFilterUser($scope.users[i]._id));
}
});
});
$scope.fnSwitchMetric = function(metric) {
fnLineChart('c2', $scope.insights, 'date', metric);
}
$scope.colors = ['#E91E63', '#3F51B5', '#00BCD4', '#8BC34A', '#FF9800', '#795548', '#607D8B', '#00CC00', '#0000CC', '#DDDDDD', '#999999', '#333333', '#990000']
$scope.pagecharts = {};
var fnLineChart = function(chart, dataset, dimension, metric) {
$scope.pagecharts[chart] = {
metric: metric,
data: dataset,
type: 'serial',
theme: 'light',
marginLeft: 0,
marginRight: 0,
marginTop: 10,
fontFamily: 'Roboto',
categoryField: dimension,
rotate: false,
pathToImages: 'vendor/amchart/images/',
dataDateFormat: 'YYYYMMDD',
categoryAxis: {
gridPosition: "start",
parseDates: true,
gridThickness: 0
},
valueAxes: [{
position: "left",
axisAlpha: 0,
labelsEnabled: true,
dashLength: 4,
color: '#dedede',
}],
graphs: [{
balloonText: $filter('translate')('metrics.' + metric) + "
[[value]]",
"bullet": "round",
"bulletBorderAlpha": 1,
"bulletColor": "#FFFFFF",
"useLineColorForBulletBorder": true,
"fillAlphas": 0.2,
"lineThickness": 2,
"lineAlpha": 1,
"bulletSize": 7,
"title": "Expenses",
"valueField": metric,
lineColor: $scope.colors[colorindex],
}],
}
colorindex += 1;
}
var fnCalcAvg = function(sites) {
var dataset = $scope.report.dataset;
// CALCULATE AVERAGE
return sites.reduce((acc, item) => {
if (typeof item.insights[dataset] != 'undefined') {
for (var y in [0, 1]) {
if (typeof item.insights[dataset][y] != 'undefined') {
for (var i in item.insights[dataset][y]) {
if (typeof item.insights[dataset][y][i] != 'undefined') {
acc[i] = acc[i] || [{ 'n': 0, 's': 0 }, { 'n': 0, 's': 0 }];
acc[i][y].s = acc[i][y].s + item.insights[dataset][y][i];
acc[i][y].n = acc[i][y].n + 1;
}
}
}
}
}
return acc;
}, {});
}
}
]);
app.controller('mcp', ['$scope', '$rootScope', '$timeout', '$location', 'h', function ($scope, $rootScope, $timeout, $location, h) {
// Visibility comes from user.site.show.mcp (reseller tools config, with a
// non-guest default when the key is not set). See permission::show().
if (!$rootScope.user || !$rootScope.user.site || !$rootScope.user.site.show || !$rootScope.user.site.show.mcp) {
$location.path('/');
return;
}
// The endpoint is always this host. The reseller is resolved from
// HTTP_HOST server-side, so a white-label customer connects to their own
// domain and needs no extra configuration.
$scope.mcp = {
host: window.location.host,
endpoint: window.location.protocol + '//' + window.location.host + '/rest/mcp/server',
skillRepo: 'https://github.com/corewebvitals/seo-superpowers',
// Claude Code plugin marketplace (see repo .claude-plugin/marketplace.json)
marketplaceAdd: '/plugin marketplace add corewebvitals/seo-superpowers',
marketplaceInstall: '/plugin install seo-superpowers@seo-superpowers',
marketplaceReload: '/reload-plugins',
revealed: false,
copied: ''
};
$scope.fnKey = function () {
return ($rootScope.user && $rootScope.user.key) ? $rootScope.user.key : '';
};
// Masked by default: the key is a permanent credential and this page gets
// screen-shared and screenshotted.
$scope.fnKeyMasked = function () {
var k = $scope.fnKey();
if (!k) { return ''; }
return k.substring(0, 8) + '••••••••••••••••••••••••••••••••' + k.substring(k.length - 4);
};
$scope.fnConfig = function () {
return JSON.stringify({
mcpServers: {
marketingtracer: {
type: 'http',
url: $scope.mcp.endpoint,
headers: { Authorization: 'Bearer ' + ($scope.mcp.revealed ? $scope.fnKey() : 'YOUR_API_KEY') }
}
}
}, null, 2);
};
$scope.fnCopy = function (what, text) {
var done = function () {
$scope.mcp.copied = what;
$timeout(function () { $scope.mcp.copied = ''; }, 2000);
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(function () {
$scope.$apply(done);
});
return;
}
// Older browsers: fall back to a throwaway textarea.
var ta = document.createElement('textarea');
ta.value = text;
document.body.appendChild(ta);
ta.select();
try { document.execCommand('copy'); } catch (e) {}
document.body.removeChild(ta);
done();
};
$scope.tools = [
{ name: 'list_sites', what: 'Find your websites and the site_id every other tool needs, plus which integrations are connected.' },
{ name: 'page_brief', what: 'Everything about one page at once: the keywords it ranks for, whether they are rising or falling, its traffic, and its on-page issues.' },
{ name: 'find_opportunities', what: 'The pages worth fixing first, ranked by optimisation potential and weighed against real search demand.' },
{ name: 'diagnose_traffic_drop', what: 'Compares a period against the one before it, says which pages lost the traffic, and whether anything was edited on them.' },
{ name: 'find_content_decay', what: 'Pages that have been sliding for months rather than dropping in one go. The decline a month-on-month view never shows.' },
{ name: 'find_content_gaps', what: 'Queries you already appear for and do not win: real demand, ranking below page one.' },
{ name: 'find_cannibalization', what: 'Queries where two or more of your pages show up for the same search term.' },
{ name: 'cluster_performance', what: 'How each section of the site performs, by URL folder: how many pages it holds, how many earn anything, and whether it is growing.' },
{ name: 'get_search_console', what: 'Search Console clicks, impressions, CTR and position, for the whole site or one page.' },
{ name: 'get_analytics', what: 'Google Analytics 4 sessions, users, conversions and revenue, for the whole site or one page.' },
{ name: 'get_scan', what: 'On-page SEO scan results: which rules fail across the site, or one page\'s issues with how to fix them.' },
{ name: 'get_ai_check', what: 'AI readiness: which AI crawlers can read the site, and per page how much the text reads like stock AI writing (beta).' }
];
// ---- demo terminal ------------------------------------------------------
//
// Example questions are the demo: clicking one runs it. Figures are invented
// and the footer says so. A convincing fake of the user's own data would
// contradict the promise that these tools never invent numbers.
var scenarios = [
{
// Shape matches a real diagnose_traffic_drop answer: site-wide may
// not be “down”; the pain is one money page + a soft section.
prompt: 'Why is our organic traffic down?',
chip: 'Why is traffic down?',
tools: [
{ name: 'diagnose_traffic_drop', detail: '28 days vs the 28 before' },
{ name: 'find_content_decay', detail: '6 months on the main loser' },
{ name: 'page_brief', detail: '/services/warehouse-coating' },
{ name: 'get_analytics', detail: 'sessions same windows' }
],
answer: [
{ t: ' site-wide (28d vs prior) recent prior change', dim: true },
{ t: ' GSC clicks 1,110 1,105 +0.5%', dim: true },
{ t: ' GSC impressions 295,000 540,000 −45%', dim: true },
{ t: ' GA sessions 4,900 4,300 +14%', dim: true },
{ t: '' },
{ t: 'Clicks are flat and sessions are up. The chart looks bad because', em: true },
{ t: 'impressions dropped. That is not the same as fewer visits.', em: true },
{ t: '' },
{ t: ' largest click losses', dim: true },
{ t: ' /services/warehouse-coating 402 → 349 pos 6.1 → 9.1', dim: true },
{ t: ' /guides/surface-prep 20 → 10', dim: true },
{ t: ' /blog/* (several) −5 to −9 each', dim: true },
{ t: '' },
{ t: 'Most of the real loss is one page. Over six months' },
{ t: '/services/warehouse-coating went 880 → 600 clicks and drifted' },
{ t: 'from position ~4 to ~6. It is missing a meta description; head' },
{ t: 'terms still get good CTR where you rank high.' },
{ t: '' },
{ t: 'What to do: update that service page (content + meta), check who' },
{ t: 'outranked you, and only then look at the small blog dips. A' },
{ t: 'site-wide rewrite is not justified by this data.' }
]
},
{
prompt: 'We rank on page 1 for a lot of keywords but barely get any clicks. Why?',
chip: 'Ranking but no clicks?',
tools: [
{ name: 'get_search_console', detail: 'queries by impressions, 90 days' },
{ name: 'page_brief', detail: 'the four commercial URLs behind them' },
{ name: 'get_scan', detail: 'titles on those pages' }
],
answer: [
{ t: ' query pos impr CTR typical', dim: true },
{ t: ' epoxy floor coating 3.1 22,100 2.4% ~11%', dim: true },
{ t: ' warehouse floor paint 2.8 14,800 2.9% ~13%', dim: true },
{ t: ' industrial floor cost 4.0 9,200 1.8% ~8%', dim: true },
{ t: '' },
{ t: 'You rank where you want. People are not clicking. At those' },
{ t: 'positions you would usually see roughly 8-12% CTR.' },
{ t: '' },
{ t: 'All four titles start with the company name, then the keyword.' },
{ t: 'They were rewritten in the same February release. Pages you did' },
{ t: 'not touch still sit near a normal click rate.' },
{ t: '' },
{ t: 'What to do: put the keyword first in the title, brand at the end,' },
{ t: 'and test that as one template change. Also glance at the SERP for' },
{ t: 'AI overviews on the more informational queries, but fix titles first.' }
]
},
{
prompt: 'What keywords do we show up for but never really rank for?',
chip: 'Keywords we almost rank for?',
tools: [
{ name: 'find_content_gaps', detail: 'impressions, average position worse than 8' },
{ name: 'get_search_console', detail: 'which URL ranks for the top gaps' }
],
answer: [
{ t: ' query pos impr clicks', dim: true },
{ t: ' commercial epoxy flooring 11.2 18,400 38', dim: true },
{ t: ' anti slip warehouse floor 12.4 11,200 22', dim: true },
{ t: ' resin floor cost calculator 14.1 8,600 14', dim: true },
{ t: ' epoxy vs polyurethane floor 10.8 7,100 31', dim: true },
{ t: '' },
{ t: 'You already get seen for these. You are just not on page one.' },
{ t: '' },
{ t: '“commercial epoxy flooring” is the priority: most impressions,' },
{ t: 'and /services/warehouse-coating is already the URL that ranks for' },
{ t: 'it. Strengthen that page (content, internal links from guides),' },
{ t: 'do not start a second page for the same intent.' },
{ t: '' },
{ t: '“resin floor cost calculator” has no good match on the site.' },
{ t: 'That one may need a new page. Check the other two queries against' },
{ t: 'existing product URLs before writing anything new.' }
]
},
{
prompt: 'Which pages used to do well and are slowly dying?',
chip: 'Pages that are slowly dying?',
tools: [
{ name: 'find_content_decay', detail: '6 months, period by period' },
{ name: 'page_brief', detail: 'the three biggest losses' }
],
answer: [
{ t: 'Three pages. No single crash. A few percent down every month.' },
{ t: '' },
{ t: ' /guides/surface-prep', dim: true },
{ t: ' 880 → 810 → 740 → 690 → 640 → 600 clicks (−31%)', dim: true },
{ t: ' Position ~4.1 → ~4.6. Impressions are what is falling.', dim: true },
{ t: '' },
{ t: ' /blog/floor-prep-checklist same shape, −28%', dim: true },
{ t: ' /blog/moisture-testing same shape, −24%', dim: true },
{ t: '' },
{ t: 'Position is almost flat, so this is not a sudden ranking collapse.' },
{ t: 'The queries that used to hit these pages are drying up, and two' },
{ t: 'newer guides now rank for terms surface-prep used to own.' },
{ t: '' },
{ t: 'What to do: refresh surface-prep first (highest absolute loss).' },
{ t: 'Merge the two overlapping prep posts into one strong guide and' },
{ t: '301 the weaker URL. Do that before publishing more in the same topic.' }
]
},
{
prompt: 'We redesigned the site last month. Did we lose anything important?',
chip: 'Did the redesign hurt us?',
tools: [
{ name: 'diagnose_traffic_drop', detail: '4 weeks after launch vs 4 weeks before' },
{ name: 'get_scan', detail: 'indexability and sitemap coverage' },
{ name: 'get_search_console', detail: 'top URLs that disappeared' }
],
answer: [
{ t: ' net clicks about −6%, but two opposite moves:', dim: true },
{ t: ' /guides/* +380 clicks new URLs indexed fine', dim: true },
{ t: ' /old-blog/* −860 clicks 41 URLs missing from the sitemap', dim: true },
{ t: '' },
{ t: 'Those 41 still return 200 and still have internal links. They were' },
{ t: 'dropped from the sitemap, not redirected. Six of them were in the' },
{ t: 'top 5 before the cutover and make up most of the 860 lost clicks.' },
{ t: '' },
{ t: 'What to do: restore or 301 those six first. Put them back in the' },
{ t: 'sitemap or redirect each to the new URL that replaced it. The other' },
{ t: '35 were under 10 clicks a month between them. Clean them up later.' },
{ t: '' },
{ t: 'The new guides section is fine. Do not roll back the redesign for this.' }
]
},
{
prompt: 'Is our blog actually bringing traffic, or is it just sitting there?',
chip: 'Is the blog doing anything?',
tools: [
{ name: 'cluster_performance', detail: 'pages by section, 90 days vs prior 90' },
{ name: 'get_search_console', detail: 'top URLs under /blog' }
],
answer: [
{ t: ' section pages with clicks clicks vs prev 90d', dim: true },
{ t: ' /guides 14 13 3,180 +18%', dim: true },
{ t: ' /products 412 46 1,940 −4%', dim: true },
{ t: ' /blog 308 19 210 −31%', dim: true },
{ t: '' },
{ t: 'The blog is large and almost empty. 308 pages, 19 with any clicks,' },
{ t: 'and two of those 19 carry most of the 210. The section is down 31%.' },
{ t: '' },
{ t: 'Fourteen guides out-earn the whole blog and are growing.' },
{ t: '' },
{ t: 'What to do: double down on the two blog posts that work (update,' },
{ t: 'internal links, better titles). Stop publishing new posts until those' },
{ t: 'are solid. Noindex or merge the long tail that earns nothing. Put the' },
{ t: 'next content effort into /guides, where the same work pays off more.' }
]
},
{
// Sprint prioritisation: data + issue → fix. Invented generic paths
// (same style as the other demos, not a real site).
prompt: 'What SEO work should we do this sprint?',
chip: 'What SEO work this sprint?',
tools: [
{ name: 'find_opportunities', detail: 'OPI × search demand, 90 days' },
{ name: 'find_content_decay', detail: '6 months, period by period' },
{ name: 'page_brief', detail: 'top candidates from the list' },
{ name: 'get_analytics', detail: 'top sessions by path' }
],
answer: [
{ t: ' page clicks impr pos sessions', dim: true },
{ t: ' /services/warehouse-coating 880 18.4k 8.2 640', dim: true },
{ t: ' /guides/surface-prep 604 9.1k 4.6 510', dim: true },
{ t: ' /blog/epoxy-vs-polyurethane 12 11.2k 14.1 40', dim: true },
{ t: ' /products/anti-slip-paint 41 7.2k 9.0 95', dim: true },
{ t: '' },
{ t: '1. /services/warehouse-coating (best ROI)', em: true },
{ t: ' Page two for a commercial query with real demand. Add a proper' },
{ t: ' meta description, expand the comparison table, and link to it' },
{ t: ' from the relevant guides so it can break page one.' },
{ t: '' },
{ t: '2. /guides/surface-prep (slow decline)', em: true },
{ t: ' Down every month for half a year while position holds. Refresh' },
{ t: ' the guide and merge the near-duplicate post so one page owns the topic.' },
{ t: '' },
{ t: '3. /blog/epoxy-vs-polyurethane (indexation)', em: true },
{ t: ' 11k impressions but marked not indexable. Check noindex, robots' },
{ t: ' and canonical; fix that before rewriting the article.' },
{ t: '' },
{ t: '4. /products/anti-slip-paint (titles)', em: true },
{ t: ' CTR ~2% at position 9 is low. Lead the title with the product' },
{ t: ' keyword, not the brand, and write a clearer meta.' },
{ t: '' },
{ t: 'Leave /careers and other high-OPI pages with no search demand alone' },
{ t: 'until the four above are done.' }
]
},
{
// find_cannibalization: query×page pairs, multiple URLs per query.
prompt: 'Are any of our pages competing for the same keywords?',
chip: 'Pages competing for keywords?',
tools: [
{ name: 'find_cannibalization', detail: 'query + page pairs, 90 days' }
],
answer: [
{ t: ' query pages impr clicks', dim: true },
{ t: ' commercial epoxy flooring 3 18.4k 41', dim: true },
{ t: ' warehouse floor coating 2 11.2k 28', dim: true },
{ t: ' epoxy vs polyurethane 2 7.1k 19', dim: true },
{ t: '' },
{ t: '1. commercial epoxy flooring', em: true },
{ t: ' /services/warehouse-coating pos 8.2 · most impressions' },
{ t: ' /blog/epoxy-vs-polyurethane pos 14 · overlapping intent' },
{ t: ' /products/epoxy-pro pos 18 · thin product page' },
{ t: ' Pick one commercial URL (usually the service page), retarget the' },
{ t: ' blog as a supporting guide, and link both to that winner.' },
{ t: '' },
{ t: '2. warehouse floor coating', em: true },
{ t: ' Service page and a guide both show up. Keep the service page as' },
{ t: ' the main result; make the guide clearly "how to" and link up.' },
{ t: '' },
{ t: 'Do not 301 everything automatically. Check intent first: product,' },
{ t: 'service and guide can share a theme without being true duplicates.' }
]
}
];
// Short labels for the buttons; the terminal types the full question.
$scope.examples = scenarios.map(function (s) { return s.chip; });
var timers = [];
var at = function (ms, fn) { timers.push($timeout(fn, ms)); };
// The panel is a fixed height and scrolls, so it has to follow the output.
var follow = function () {
timers.push($timeout(function () {
var el = document.getElementById('mcpTerminal');
if (el) { el.scrollTop = el.scrollHeight; }
}));
};
$scope.demo = { prompt: '', tools: [], answer: [], running: false, index: 0 };
$scope.fnDemoRun = function (which) {
angular.forEach(timers, function (t) { $timeout.cancel(t); });
timers = [];
var s = scenarios[which % scenarios.length];
$scope.demo = { prompt: '', tools: [], answer: [], running: true, index: which % scenarios.length };
var el = document.getElementById('mcpTerminal');
if (el) { el.scrollTop = 0; }
var clock = 0;
// Type the question a character at a time.
for (var i = 1; i <= s.prompt.length; i++) {
(function (n) {
at(clock += 18, function () { $scope.demo.prompt = s.prompt.substring(0, n); });
})(i);
}
// Each tool appears, spins, then settles.
angular.forEach(s.tools, function (tool) {
at(clock += 380, function () {
$scope.demo.tools.push({ name: tool.name, detail: tool.detail, done: false });
follow();
});
at(clock += 700, function () {
$scope.demo.tools[$scope.demo.tools.length - 1].done = true;
});
});
// Then the answer, line by line.
clock += 350;
angular.forEach(s.answer, function (line) {
at(clock += (line.t === '' ? 45 : 150), function () {
$scope.demo.answer.push(line);
follow();
});
});
at(clock += 400, function () { $scope.demo.running = false; });
};
$scope.$on('$destroy', function () {
angular.forEach(timers, function (t) { $timeout.cancel(t); });
timers = [];
});
$scope.fnDemoRun(0);
}]);
app.controller('uptime', ['$scope', '$rootScope', '$http', '$location', '$translate', 'h', function ($scope, $rootScope, $http, $location, $translate, h) {
// Zichtbaar via user.site.show.uptime (reseller tools config, standaard aan). Zie permission::show().
if (!$rootScope.user || !$rootScope.user.site || !$rootScope.user.site.show || !$rootScope.user.site.show.uptime) {
$location.path('/');
return;
}
$scope.oUptime = {
loading: false,
saving: false,
data: null,
chart: null,
init: function () {
var self = this;
self.loading = true;
$http.get('/rest/uptime/get', { params: { websiteid: h.siteId() } }).then(function (res) {
self.data = res.data;
self.chart = self.fnChart(res.data.series || []);
self.loading = false;
}, function () {
self.loading = false;
});
},
fnSet: function (enabled) {
var self = this;
self.saving = true;
$http.post('/rest/uptime/set', { websiteid: h.siteId(), enabled: enabled }).then(function () {
self.saving = false;
self.init();
}, function () {
self.saving = false;
});
},
// reactietijd per check; storingen zonder antwoord tellen niet mee
fnChart: function (series) {
var data = [];
angular.forEach(series, function (s) {
if (s.ttfb !== null && s.outcome !== 'down') {
data.push({ date: new Date(s.at), ttfb: s.ttfb });
}
});
if (!data.length) {
return null;
}
return {
data: data,
type: 'serial',
categoryField: 'date',
categoryAxis: { parseDates: true, minPeriod: 'mm', gridThickness: 0 },
valueAxes: [{ position: 'left', axisAlpha: 0, dashLength: 4, color: '#9ca3af', unit: ' ms', minimum: 0 }],
graphs: [{
valueField: 'ttfb',
type: 'line',
lineColor: '#3b82f6',
fillAlphas: 0.1,
lineThickness: 2,
balloonText: 'TTFB [[value]] ms'
}]
};
},
fnDate: function (ms, withTime) {
if (!ms) {
return '';
}
var d = new Date(ms);
var opts = { weekday: 'short', day: 'numeric', month: 'short' };
if (d.getFullYear() !== new Date().getFullYear()) {
opts.year = 'numeric';
}
if (withTime) {
opts.hour = '2-digit';
opts.minute = '2-digit';
}
return d.toLocaleString($translate.use(), opts);
},
fnDuration: function (inc) {
var end = inc.end || Date.now();
var min = Math.max(30, Math.round((end - inc.start) / 60000));
if (min < 60) {
return min + ' min';
}
var hrs = Math.round(min / 6) / 10;
if (hrs < 48) {
return String(hrs).replace('.', ',') + ' u';
}
return Math.round(hrs / 24) + ' d';
},
fnAgo: function (ms) {
if (!ms) {
return '';
}
return Math.max(1, Math.round((Date.now() - ms) / 60000));
},
fnCertDays: function () {
if (!this.data || !this.data.cert_expires) {
return null;
}
return Math.floor((this.data.cert_expires - Date.now()) / 86400000);
},
fnBarClass: function (bar) {
return {
up: 'bg-green-500',
warn: 'bg-amber-400',
blocked: 'bg-amber-400',
down: 'bg-red-500'
}[bar.outcome] || 'bg-gray-200';
},
fnBarTitle: function (bar) {
var t = this.fnDate(bar.at, true);
if (!bar.outcome) {
return t;
}
var s = $translate.instant('uptime.state.' + bar.outcome);
if (bar.outcome !== 'up' && (bar.status || bar.error)) {
s += ': ' + (bar.status || bar.error);
}
return t + ' · ' + s;
},
fnReason: function (inc) {
if (inc.mitigated) {
return inc.status + ', ' + $translate.instant('uptime.firewall');
}
if (inc.error) {
return inc.error_text || inc.error;
}
return inc.status ? String(inc.status) : '';
}
};
$scope.oUptime.init();
}]);
// Korte verwijzing naar de firewallinstellingen, op elke pagina waar de MarketingTracer-bot geblokkeerd
// kan worden (uptime, SEO-scan): .
// Altijd zichtbaar; de link alleen voor wie de instellingen mag zien (rol 5 of lager).
app.directive('botFirewallRef', ['$rootScope', function ($rootScope) {
return {
restrict: 'E',
scope: { blocked: '=?' },
template: '',
link: function (scope) {
scope.canEdit = function () {
return $rootScope.user && $rootScope.user.site && $rootScope.user.site.role < 6;
};
}
};
}]);
// Site-instellingen (/settings/site#firewall): de geheime waarde van de site en de firewallregels voor
// de MarketingTracer-bot (SEO-scan en uptime). Zie rest/include/bot.php.
app.controller('botfirewall', ['$scope', '$http', '$location', '$anchorScroll', '$timeout', 'h', function ($scope, $http, $location, $anchorScroll, $timeout, h) {
$scope.oFirewall = {
data: null,
revealed: false,
copied: '',
// Gemaskeerd tot je op "tonen" klikt: deze pagina wordt gedeeld en gescreenshot.
fnMasked: function (token) {
return token ? token.substring(0, 4) + '••••••••••••••••••••••' + token.substring(token.length - 4) : '';
},
fnMaskRule: function (rule) {
var t = this.data && this.data.token;
return t ? rule.split(t).join(this.fnMasked(t)) : rule;
},
fnCopy: function (what, text) {
var self = this;
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(function () {
$scope.$apply(function () {
self.copied = what;
});
});
}
}
};
$http.get('/rest/bot/firewall', { params: { websiteid: h.siteId() } }).then(function (res) {
$scope.oFirewall.data = res.data;
// link vanaf /uptime en de scan: /settings/site#firewall
if ($location.hash() === 'firewall') {
$timeout(function () {
$anchorScroll('firewall');
});
}
});
}]);