/** * Model for Multisites Dashboard aka All Websites Dashboard. * */ angular.module('piwikApp').factory('multisitesDashboardModel', function (piwikApi, $filter, $timeout) { /** * * this is the list of all available sites. For performance reason this list is different to model.sites. ngRepeat * won't operate on the whole list this way. The allSites array contains websites and groups in the following * structure * * - website1 * - website2 * - website3.sites = [ // this is a group * - website4 * - website5 * ] * - website6 * * This structure allows us to display the sites within a group directly under the group without big logic and also * allows us to calculate the summary for each group easily */ var allSitesByGroup = []; var model = {}; // those sites are going to be displayed model.sites = []; model.isLoading = false; model.pageSize = 5; model.currentPage = 0; model.totalVisits = '?'; model.totalActions = '?'; model.totalRevenue = '?'; model.searchTerm = ''; model.lastVisits = '?'; model.lastVisitsDate = '?'; fetchPreviousSummary(); // create a new group object which has similar structure than a website function createGroup(name){ return { label: name, sites: [], nb_visits: 0, nb_pageviews: 0, revenue: 0, isGroup: true }; } // create a new group with empty site to make sure we do not change the original group in $allSites function copyGroup(group) { return { label: group.label, sites: [], nb_visits: group.nb_visits, nb_pageviews: group.nb_pageviews, revenue: group.revenue, isGroup: true }; } function onError () { model.errorLoadingSites = true; model.sites = []; allSitesByGroup = []; } function calculateMetricsForEachGroup(groups) { angular.forEach(groups, function (group) { angular.forEach(group.sites, function (site) { var revenue = (site.revenue+'').match(/(\d+\.?\d*)/); // convert $ 0.00 to 0.00 or 5€ to 5 group.nb_visits += parseInt(site.nb_visits, 10); group.nb_pageviews += parseInt(site.nb_pageviews, 10); if (revenue.length) { group.revenue += parseInt(revenue[0], 10); } }); }); } function createGroupsAndMoveSitesIntoRelatedGroup(allSitesUnordered, reportMetadata) { var sitesByGroup = []; var groups = {}; // we do 3 things (complete site information, create groups, move sites into group) in one step for // performance reason, there can be > 20k sites angular.forEach(allSitesUnordered, function (site, index) { site.idsite = reportMetadata[index].idsite; site.group = reportMetadata[index].group; site.main_url = reportMetadata[index].main_url; if (site.group) { if (!groups[site.group]) { var group = createGroup(site.group); groups[site.group] = group; sitesByGroup.push(group); } groups[site.group].sites.push(site); } else { sitesByGroup.push(site); } }); calculateMetricsForEachGroup(groups); return sitesByGroup; } model.updateWebsitesList = function (processedReport) { if (!processedReport) { onError(); return; } var allSitesUnordered = processedReport.reportData; model.totalVisits = processedReport.reportTotal.nb_visits; model.totalActions = processedReport.reportTotal.nb_actions; model.totalRevenue = processedReport.reportTotal.revenue; allSitesByGroup = createGroupsAndMoveSitesIntoRelatedGroup(allSitesUnordered, processedReport.reportMetadata); if (!allSitesByGroup.length) { return; } if (model.searchTerm) { model.searchSite(model.searchTerm); } else { model.sites = allSitesByGroup; } }; model.getNumberOfFilteredSites = function () { var numSites = model.sites.length; var groupNames = {}; for (var index = 0; index < model.sites.length; index++) { var site = model.sites[index]; if (site && site.isGroup) { numSites += site.sites.length; } } return numSites; }; model.getNumberOfPages = function () { return Math.ceil(model.getNumberOfFilteredSites() / model.pageSize - 1); }; model.getCurrentPagingOffsetStart = function() { return Math.ceil(model.currentPage * model.pageSize); }; model.getCurrentPagingOffsetEnd = function() { var end = model.getCurrentPagingOffsetStart() + parseInt(model.pageSize, 10); var max = model.getNumberOfFilteredSites(); if (end > max) { end = max; } return parseInt(end, 10); }; model.previousPage = function () { model.currentPage = model.currentPage - 1; }; model.nextPage = function () { model.currentPage = model.currentPage + 1; }; function nestedSearch(sitesByGroup, term) { var filteredSites = []; for (var index in sitesByGroup) { var site = sitesByGroup[index]; if (site.isGroup) { var matchingSites = nestedSearch(site.sites, term); if (matchingSites.length || (''+site.label).toLowerCase().indexOf(term) > -1) { var clonedGroup = copyGroup(site); clonedGroup.sites = matchingSites; filteredSites.push(clonedGroup); } } else if ((''+site.label).toLowerCase().indexOf(term) > -1) { filteredSites.push(site); } else if (site.group && (''+site.group).toLowerCase().indexOf(term) > -1) { filteredSites.push(site); } } return filteredSites; } model.searchSite = function (term) { model.searchTerm = term; model.currentPage = 0; model.sites = nestedSearch(allSitesByGroup, term); }; function fetchPreviousSummary () { piwikApi.fetch({ method: 'API.getLastDate' }).then(function (response) { if (response && response.value) { return response.value; } }).then(function (lastDate) { if (!lastDate) { return; } model.lastVisitsDate = lastDate; return piwikApi.fetch({ method: 'API.getProcessedReport', apiModule: 'MultiSites', apiAction: 'getAll', hideMetricsDoc: '1', filter_limit: '0', showColumns: 'label,nb_visits', enhanced: 1, date: lastDate }); }).then(function (response) { if (response && response.reportTotal) { model.lastVisits = response.reportTotal.nb_visits; } }); } model.fetchAllSites = function (refreshInterval) { if (model.isLoading) { piwikApi.abort(); } model.isLoading = true; model.errorLoadingSites = false; return piwikApi.fetch({ method: 'API.getProcessedReport', apiModule: 'MultiSites', apiAction: 'getAll', hideMetricsDoc: '1', filter_limit: '-1', showColumns: 'label,nb_visits,nb_pageviews,visits_evolution,pageviews_evolution,revenue_evolution,nb_actions,revenue', enhanced: 1 }).then(function (response) { model.updateWebsitesList(response); }, onError)['finally'](function () { model.isLoading = false; if (refreshInterval && refreshInterval > 0) { $timeout(function () { model.fetchAllSites(refreshInterval); }, refreshInterval * 1000); } }); }; return model; });