mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-08-21 05:45:13 +00:00

* master: Adding task servers to db firewall. Adding redis cache. Returning favicon server. Better munin plugins for redis and mongo. Removing unused redis keys for shared/comments. Dry-run. Adding read preference to migration. Migration to create story_hash for missing feeds. Making a clearer description of slow feeds. Upping feed fetches, slowing down fetch interval for less active feeds, setting premium expire time to one year from most recent payment date (as opposed to borked payment gap logic), and adding logginf or tasking feeds. Switching check for HAProxy'ied socket.io to dev instead of www. Thanks @anaconda! Sync delay in mongodb and updating story hash migration. Upping feed fetches. Fix force close when attempting to fetch a user with a null user.id value Fix force close updating ProfileActivityFragment if no profile response was received Move creating ImageLoader in NewsBlurApplication to onCreate to ensure that base context has been set and prevent force close Boosting fetches. Fix duplicate requirement Conflicts: fabfile.py
296 lines
No EOL
13 KiB
JavaScript
296 lines
No EOL
13 KiB
JavaScript
NEWSBLUR.ReaderStatistics = function(feed_id, options) {
|
|
var defaults = {};
|
|
|
|
this.options = $.extend({}, defaults, options);
|
|
this.model = NEWSBLUR.assets;
|
|
this.feed_id = feed_id;
|
|
this.feed = this.model.get_feed(feed_id);
|
|
this.feeds = this.model.get_feeds();
|
|
this.first_load = true;
|
|
this.runner();
|
|
};
|
|
|
|
NEWSBLUR.ReaderStatistics.prototype = new NEWSBLUR.Modal;
|
|
NEWSBLUR.ReaderStatistics.prototype.constructor = NEWSBLUR.ReaderStatistics;
|
|
|
|
_.extend(NEWSBLUR.ReaderStatistics.prototype, {
|
|
|
|
runner: function() {
|
|
var self = this;
|
|
|
|
this.initialize_feed(this.feed_id);
|
|
this.make_modal();
|
|
this.open_modal();
|
|
setTimeout(function() {
|
|
self.get_stats();
|
|
}, 50);
|
|
|
|
this.$modal.bind('click', $.rescope(this.handle_click, this));
|
|
this.$modal.bind('change', $.rescope(this.handle_change, this));
|
|
},
|
|
|
|
make_modal: function() {
|
|
var self = this;
|
|
|
|
this.$modal = $.make('div', { className: 'NB-modal-statistics NB-modal' }, [
|
|
$.make('div', { className: 'NB-modal-feed-chooser-container'}, [
|
|
this.make_feed_chooser()
|
|
]),
|
|
$.make('div', { className: 'NB-modal-loading' }),
|
|
$.make('h2', { className: 'NB-modal-title' }, 'Statistics & History'),
|
|
$.make('h2', { className: 'NB-modal-subtitle' }, [
|
|
$.make('img', { className: 'NB-modal-feed-image feed_favicon', src: $.favicon(this.feed) }),
|
|
$.make('div', { className: 'NB-modal-feed-heading' }, [
|
|
$.make('span', { className: 'NB-modal-feed-title' }, this.feed.get('feed_title')),
|
|
$.make('span', { className: 'NB-modal-feed-subscribers' }, Inflector.pluralize(' subscriber', this.feed.get('num_subscribers'), true))
|
|
])
|
|
]),
|
|
$.make('div', { className: 'NB-modal-statistics-info' })
|
|
]);
|
|
|
|
var $stats = this.make_stats({
|
|
'last_update': '',
|
|
'next_update': '',
|
|
'loading': true
|
|
});
|
|
$('.NB-modal-statistics-info', this.$modal).replaceWith($stats);
|
|
},
|
|
|
|
get_stats: function() {
|
|
var $loading = $('.NB-modal-loading', this.$modal);
|
|
$loading.addClass('NB-active');
|
|
|
|
var statistics_fn = this.options.social_feed ? this.model.get_social_statistics : this.model.get_feed_statistics;
|
|
statistics_fn.call(this.model, this.feed_id, $.rescope(this.populate_stats, this));
|
|
},
|
|
|
|
populate_stats: function(s, data) {
|
|
var self = this;
|
|
|
|
NEWSBLUR.log(['Stats', data]);
|
|
|
|
var $loading = $('.NB-modal-loading', this.$modal);
|
|
$loading.removeClass('NB-active');
|
|
|
|
var $stats = this.make_stats(data);
|
|
$('.NB-modal-statistics-info', this.$modal).replaceWith($stats);
|
|
|
|
setTimeout(function() {
|
|
self.make_charts(data);
|
|
}, this.first_load ? 200 : 50);
|
|
|
|
setTimeout(function() {
|
|
$.modal.impl.resize(self.$modal);
|
|
}, 100);
|
|
},
|
|
|
|
make_stats: function(data) {
|
|
var update_interval = NEWSBLUR.utils.calculate_update_interval(data['update_interval_minutes']);
|
|
var premium_update_interval = NEWSBLUR.utils.calculate_update_interval(data['premium_update_interval_minutes']);
|
|
|
|
var $stats = $.make('div', { className: 'NB-modal-statistics-info' }, [
|
|
(!this.options.social_feed && $.make('div', { className: 'NB-statistics-stat NB-statistics-updates'}, [
|
|
$.make('div', { className: 'NB-statistics-update'}, [
|
|
$.make('div', { className: 'NB-statistics-label' }, 'Last Update'),
|
|
$.make('div', { className: 'NB-statistics-count' }, ' ' + (data['last_update'] && (data['last_update'] + ' ago')))
|
|
]),
|
|
$.make('div', { className: 'NB-statistics-update'}, [
|
|
$.make('div', { className: 'NB-statistics-label' }, 'Every'),
|
|
$.make('div', { className: 'NB-statistics-count' }, update_interval)
|
|
]),
|
|
$.make('div', { className: 'NB-statistics-update'}, [
|
|
$.make('div', { className: 'NB-statistics-label' }, 'Next Update'),
|
|
(data.active && $.make('div', { className: 'NB-statistics-count' }, ' ' + (data['next_update'] && ('in ' + data['next_update'])))),
|
|
(!data.active && !data.loading && $.make('div', { className: 'NB-statistics-count' }, "Not active"))
|
|
]),
|
|
((data.average_stories_per_month == 0 || data.stories_last_month == 0) &&
|
|
$.make('div', { className: 'NB-statistics-update-explainer' }, [
|
|
$.make('b', 'Why so infrequently?'),
|
|
'This site has published zero stories in the past month or has averaged less than a single story a month. As soon as it starts publishing at least once a month, it will automatically fetch more frequently.'
|
|
])),
|
|
(!NEWSBLUR.Globals.is_premium && $.make('div', { className: 'NB-statistics-premium-stats' }, [
|
|
$.make('div', { className: 'NB-statistics-update'}, [
|
|
$.make('div', { className: 'NB-statistics-label' }, [
|
|
'If you went ',
|
|
$.make('a', { href: '#', className: 'NB-premium-link NB-splash-link' }, 'premium'),
|
|
', ',
|
|
$.make('br'),
|
|
'this site would update every'
|
|
]),
|
|
$.make('div', { className: 'NB-statistics-count' }, premium_update_interval)
|
|
])
|
|
]))
|
|
])),
|
|
$.make('div', { className: 'NB-statistics-stat NB-statistics-history'}, [
|
|
$.make('div', { className: 'NB-statistics-history-stat' }, [
|
|
$.make('div', { className: 'NB-statistics-label' }, 'Stories per month')
|
|
]),
|
|
$.make('canvas', { id: 'NB-statistics-history-chart', className: 'NB-statistics-history-chart' })
|
|
]),
|
|
(data.classifier_counts && $.make('div', { className: 'NB-statistics-state NB-statistics-classifiers' }, [
|
|
this.make_classifier_count('tag', data.classifier_counts['tag']),
|
|
this.make_classifier_count('author', data.classifier_counts['author']),
|
|
this.make_classifier_count('title', data.classifier_counts['title']),
|
|
this.make_classifier_count('feed', data.classifier_counts['feed'])
|
|
])),
|
|
(!this.options.social_feed && $.make('div', { className: 'NB-statistics-stat NB-statistics-fetches'}, [
|
|
$.make('div', { className: 'NB-statistics-fetches-half'}, [
|
|
$.make('div', { className: 'NB-statistics-label' }, 'Feed Fetch'),
|
|
$.make('div', this.make_history(data, 'feed_fetch'))
|
|
]),
|
|
$.make('div', { className: 'NB-statistics-fetches-half'}, [
|
|
$.make('div', { className: 'NB-statistics-label' }, 'Page Fetch'),
|
|
$.make('div', this.make_history(data, 'page_fetch'))
|
|
]),
|
|
$.make('div', { className: 'NB-statistics-fetches-half'}, [
|
|
$.make('div', { className: 'NB-statistics-label' }, 'Feed Push'),
|
|
$.make('div', this.make_history(data, 'feed_push'))
|
|
])
|
|
]))
|
|
]);
|
|
|
|
return $stats;
|
|
},
|
|
|
|
make_classifier_count: function(facet, data) {
|
|
var self = this;
|
|
if (!data) return;
|
|
|
|
var $facets = $.make('div', { className: 'NB-statistics-facets' }, [
|
|
$.make('div', { className: 'NB-statistics-facet-title' }, Inflector.pluralize(facet, data.length))
|
|
]);
|
|
|
|
var max = 10;
|
|
_.each(data, function(v) {
|
|
if (v.pos > max || v.neg > max) {
|
|
max = Math.max(v.pos, v.neg);
|
|
}
|
|
});
|
|
|
|
var max_width = 100;
|
|
var multiplier = max_width / parseFloat(max, 10);
|
|
var calculate_width = function(count) {
|
|
return Math.max(1, multiplier * count);
|
|
};
|
|
|
|
_.each(data, function(counts) {
|
|
var pos = counts.pos || 0;
|
|
var neg = counts.neg || 0;
|
|
var key = counts[facet];
|
|
if (facet == 'feed') {
|
|
key = [$.make('div', [
|
|
$.make('img', { className: 'NB-modal-feed-image feed_favicon', src: $.favicon(self.feed) }),
|
|
$.make('span', { className: 'NB-modal-feed-title' }, self.feed.get('feed_title'))
|
|
])];
|
|
}
|
|
if (!key || (!pos && !neg)) return;
|
|
var $facet = $.make('div', { className: 'NB-statistics-facet' }, [
|
|
(pos && $.make('div', { className: 'NB-statistics-facet-pos' }, [
|
|
$.make('div', { className: 'NB-statistics-facet-bar' }).css('width', calculate_width(pos)),
|
|
$.make('div', { className: 'NB-statistics-facet-count' }, Inflector.pluralize(' like', pos, true)).css('margin-left', calculate_width(pos)+5)
|
|
])),
|
|
(neg && $.make('div', { className: 'NB-statistics-facet-neg' }, [
|
|
$.make('div', { className: 'NB-statistics-facet-bar' }).css('width', calculate_width(neg)),
|
|
$.make('div', { className: 'NB-statistics-facet-count' }, Inflector.pluralize(' dislike', neg, true)).css('margin-right', calculate_width(neg)+5)
|
|
])),
|
|
$.make('div', { className: 'NB-statistics-facet-separator' }),
|
|
$.make('div', { className: 'NB-statistics-facet-name' }, key)
|
|
]);
|
|
$facets.append($facet);
|
|
});
|
|
|
|
return $facets;
|
|
},
|
|
|
|
make_history: function(data, fetch_type) {
|
|
var fetches = data[fetch_type+'_history'];
|
|
var $history;
|
|
|
|
if (!fetches || !fetches.length) {
|
|
$history = $.make('div', { className: 'NB-history-empty' }, "Nothing recorded.");
|
|
} else {
|
|
$history = _.map(fetches, function(fetch) {
|
|
var feed_ok = _.contains([200, 304], fetch.status_code) || !fetch.status_code;
|
|
var status_class = feed_ok ? ' NB-ok ' : ' NB-errorcode ';
|
|
return $.make('div', { className: 'NB-history-fetch' + status_class, title: feed_ok ? '' : fetch.exception }, [
|
|
$.make('div', { className: 'NB-history-fetch-date' }, fetch.fetch_date || fetch.push_date),
|
|
$.make('div', { className: 'NB-history-fetch-message' }, [
|
|
fetch.message,
|
|
(fetch.status_code && $.make('div', { className: 'NB-history-fetch-code' }, ' ('+fetch.status_code+')'))
|
|
])
|
|
]);
|
|
});
|
|
}
|
|
|
|
return $history;
|
|
},
|
|
|
|
make_charts: function(data) {
|
|
var labels = _.map(data['story_count_history'], function(date) {
|
|
var date_matched = date[0].match(/(\d{4})-(\d{1,2})/);
|
|
var date = (new Date(parseInt(date_matched[1], 10), parseInt(date_matched[2],10)-1));
|
|
return NEWSBLUR.utils.shortMonthNames[date.getMonth()] + " " + date.getUTCFullYear();
|
|
});
|
|
if (labels.length > 16) {
|
|
var cut_size = Math.round(labels.length / 16.0);
|
|
labels = _.map(labels, function(label, c) {
|
|
if ((c % cut_size) == 0) return label;
|
|
return "";
|
|
});
|
|
}
|
|
var values = _.map(data['story_count_history'], function(date) {
|
|
return date[1];
|
|
});
|
|
var points = {
|
|
labels: labels,
|
|
datasets: [
|
|
{
|
|
fillColor : "rgba(151,187,205,0.5)",
|
|
strokeColor : "rgba(151,187,205,1)",
|
|
pointColor : "rgba(151,187,205,1)",
|
|
pointStrokeColor : "#fff",
|
|
data : values
|
|
}
|
|
]
|
|
};
|
|
var $plot = $(".NB-statistics-history-chart");
|
|
var width = $plot.width();
|
|
var height = $plot.height();
|
|
$plot.attr('width', width);
|
|
$plot.attr('height', height);
|
|
var myLine = new Chart($plot.get(0).getContext("2d")).Line(points, {
|
|
scaleLabel : "<%= Math.round(value) %>"
|
|
});
|
|
},
|
|
|
|
close_and_load_premium: function() {
|
|
this.close(function() {
|
|
NEWSBLUR.reader.open_feedchooser_modal();
|
|
});
|
|
},
|
|
|
|
// ===========
|
|
// = Actions =
|
|
// ===========
|
|
|
|
handle_change: function(elem, e) {
|
|
var self = this;
|
|
|
|
$.targetIs(e, { tagSelector: '.NB-modal-feed-chooser' }, function($t, $p){
|
|
var feed_id = $t.val();
|
|
self.first_load = false;
|
|
self.initialize_feed(feed_id);
|
|
self.get_stats();
|
|
});
|
|
},
|
|
|
|
handle_click: function(elem, e) {
|
|
var self = this;
|
|
|
|
$.targetIs(e, { tagSelector: '.NB-premium-link' }, function($t, $p) {
|
|
e.preventDefault();
|
|
self.close_and_load_premium();
|
|
});
|
|
}
|
|
|
|
}); |