From 099148b2fbf2e23659eab066af63104ba73e696c Mon Sep 17 00:00:00 2001 From: herby2212 Date: Tue, 20 Jul 2021 15:29:59 +0200 Subject: [PATCH 01/31] Additions count by media type graph --- data/interfaces/default/graphs.html | 35 ++++++++- .../js/graphs/additions_by_media_type.js | 73 +++++++++++++++++++ plexpy/graphs.py | 52 +++++++++++++ plexpy/webserve.py | 14 ++++ 4 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 data/interfaces/default/js/graphs/additions_by_media_type.js diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index 823378c7..56616ede 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -122,6 +122,22 @@ +
+
+

Addition count by media type Last 30 days

+

+ The combined total of tv, movies, and music added to the server. +

+
+
+
+ Loading chart... +
+
+
+
+
+
@@ -306,6 +322,7 @@ function getGraphColors(data_series) { var colors = { + 'Shows': '#E5A00D', 'TV': '#E5A00D', 'Movies': '#FFFFFF', 'Music': '#F06464', @@ -321,6 +338,7 @@ return series_colors; } + @@ -471,7 +489,7 @@ $.ajax({ url: "get_plays_by_top_10_users", type: 'get', - data: { time_range: time_range, y_axis: yaxis, user_id: selected_user_id }, + data: { time_range: time_range, y_axis: yaxis, user_id: selected_user_id }, dataType: "json", success: function(data) { if (yaxis === 'duration') { dataSecondsToHours(data); } @@ -482,6 +500,19 @@ } }); + $.ajax({ + url: "get_additions_by_media_type", + type: 'get', + data: { time_range: time_range }, + dataType: "json", + success: function(data) { + hc_additions_by_media_type_options.xAxis.categories = data.categories; + hc_additions_by_media_type_options.series = getGraphVisibility(hc_additions_by_media_type_options.chart.renderTo, data.series); + hc_additions_by_media_type_options.colors = getGraphColors(data.series); + var hc_additions_by_media_type = new Highcharts.Chart(hc_additions_by_media_type_options); + } + }); + $('#nav-tabs-plays').tab('show'); } @@ -737,6 +768,7 @@ hc_plays_by_source_resolution_options.yAxis.labels.formatter = yaxis_format; hc_plays_by_stream_resolution_options.yAxis.labels.formatter = yaxis_format; hc_plays_by_platform_by_stream_type_options.yAxis.labels.formatter = yaxis_format; + hc_additions_by_media_type_options.yAxis.labels.formatter = yaxis_format; hc_plays_by_user_by_stream_type_options.yAxis.labels.formatter = yaxis_format; hc_plays_by_month_options.yAxis.labels.formatter = yaxis_format; @@ -744,6 +776,7 @@ hc_plays_by_dayofweek_options.tooltip.formatter = tooltip_format; hc_plays_by_hourofday_options.tooltip.formatter = tooltip_format; hc_plays_by_platform_options.tooltip.formatter = tooltip_format; + hc_additions_by_media_type_options.tooltip.formatter = tooltip_format; hc_plays_by_user_options.tooltip.formatter = tooltip_format; hc_plays_by_stream_type_options.tooltip.formatter = tooltip_format; hc_plays_by_source_resolution_options.tooltip.formatter = tooltip_format; diff --git a/data/interfaces/default/js/graphs/additions_by_media_type.js b/data/interfaces/default/js/graphs/additions_by_media_type.js new file mode 100644 index 00000000..8a25910c --- /dev/null +++ b/data/interfaces/default/js/graphs/additions_by_media_type.js @@ -0,0 +1,73 @@ +var hc_additions_by_media_type_options = { + chart: { + type: 'column', + backgroundColor: 'rgba(0,0,0,0)', + renderTo: 'graph_additions_by_media_type' + }, + title: { + text: '' + }, + legend: { + enabled: true, + itemStyle: { + font: '9pt "Open Sans", sans-serif', + color: '#A0A0A0' + }, + itemHoverStyle: { + color: '#FFF' + }, + itemHiddenStyle: { + color: '#444' + } + }, + credits: { + enabled: false + }, + xAxis: { + categories: [{}], + labels: { + style: { + color: '#aaa' + } + } + }, + yAxis: { + title: { + text: null + }, + labels: { + style: { + color: '#aaa' + } + }, + stackLabels: { + enabled: false, + style: { + color: '#fff' + } + } + }, + plotOptions: { + column: { + borderWidth: 0, + stacking: 'normal', + dataLabels: { + enabled: false, + style: { + color: '#000' + } + } + }, + series: { + events: { + legendItemClick: function () { + setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name); + } + } + } + }, + tooltip: { + shared: true + }, + series: [{}] +}; \ No newline at end of file diff --git a/plexpy/graphs.py b/plexpy/graphs.py index 496a44e5..4f8afef9 100644 --- a/plexpy/graphs.py +++ b/plexpy/graphs.py @@ -421,6 +421,58 @@ class Graphs(object): 'series': series_output} return output + def get_total_additions_by_media_type(self, time_range='30'): + monitor_db = database.MonitorDatabase() + + time_range = helpers.cast_to_int(time_range) or 30 + timestamp = helpers.timestamp() - time_range * 24 * 60 * 60 + + try: + query = 'SELECT '\ + 'SUM(CASE WHEN raM.media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ + 'SUM(CASE WHEN NOT raM.grandparent_rating_key = "" THEN 1 ELSE 0 END) AS tv_count ' \ + 'FROM (SELECT * ' \ + ' FROM recently_added ' \ + ' WHERE media_type = "movie" AND added_at >= %s) AS raM ' \ + 'UNION ALL ' \ + 'SELECT ' \ + 'SUM(CASE WHEN raG.media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ + 'SUM(CASE WHEN NOT raG.grandparent_rating_key = "" THEN 1 ELSE 0 END) AS tv_count ' \ + 'FROM (SELECT * ' \ + ' FROM recently_added ' \ + ' WHERE NOT grandparent_rating_key = "" AND added_at >= %s '\ + ' GROUP BY grandparent_rating_key) AS raG ' % (timestamp, timestamp) + + result = monitor_db.select(query) + + except Exception as e: + logger.warn("Tautulli Graphs :: Unable to execute database query for get_total_additions_by_media_type: %s." % e) + return None + + categories = ["Movies", "Shows"] + series_1 = [] + series_2 = [] + + for item in result: + series_1.append(item['movie_count']) + series_2.append(item['tv_count']) + + series_1_output = {'name': 'Movies', + 'data': series_1} + series_2_output = {'name': 'Shows', + 'data': series_2} + + series_output = [] + if libraries.has_library_type('movie'): + series_output.append(series_1_output) + if libraries.has_library_type('show'): + series_output.append(series_2_output) + + output = {'categories': categories, + 'series': series_output} + return output + + def get_total_plays_per_month(self, time_range='12', y_axis='plays', user_id=None, grouping=None): monitor_db = database.MonitorDatabase() diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 8e1baa6b..9399f045 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -2458,6 +2458,20 @@ class WebInterface(object): else: logger.warn("Unable to retrieve data for get_plays_by_top_10_users.") return result + + @cherrypy.expose + @cherrypy.tools.json_out() + @requireAuth() + #called additions instead of adds so it isn't blocked by adblockers... + def get_additions_by_media_type(self, time_range='30'): + graph = graphs.Graphs() + result = graph.get_total_additions_by_media_type(time_range=time_range) + + if result: + return result + else: + logger.warn("Unable to retrieve data for get_additions_by_media_type.") + return result @cherrypy.expose @cherrypy.tools.json_out() From 5fa5e68c7cfd88372abdfa552fbacf36f58d3126 Mon Sep 17 00:00:00 2001 From: herby2212 Date: Tue, 20 Jul 2021 16:42:59 +0200 Subject: [PATCH 02/31] addition of seasons and episodes count --- data/interfaces/default/graphs.html | 4 ++- plexpy/graphs.py | 54 ++++++++++++++++++++++++----- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index 56616ede..c45de27a 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -322,7 +322,9 @@ function getGraphColors(data_series) { var colors = { - 'Shows': '#E5A00D', + 'Shows': '#669900', + 'Seasons': '#99cc00', + 'Episodes': '#cccc00', 'TV': '#E5A00D', 'Movies': '#FFFFFF', 'Music': '#F06464', diff --git a/plexpy/graphs.py b/plexpy/graphs.py index 4f8afef9..7ce2858c 100644 --- a/plexpy/graphs.py +++ b/plexpy/graphs.py @@ -428,20 +428,34 @@ class Graphs(object): timestamp = helpers.timestamp() - time_range * 24 * 60 * 60 try: - query = 'SELECT '\ + query = 'SELECT ' \ 'SUM(CASE WHEN raM.media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ - 'SUM(CASE WHEN NOT raM.grandparent_rating_key = "" THEN 1 ELSE 0 END) AS tv_count ' \ + '0 AS tv_count, ' \ + '0 AS season_count, ' \ + 'SUM(CASE WHEN raM.media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ 'FROM (SELECT * ' \ ' FROM recently_added ' \ - ' WHERE media_type = "movie" AND added_at >= %s) AS raM ' \ + ' WHERE (media_type = "movie" OR media_type = "episode") AND added_at >= %s) AS raM ' \ 'UNION ALL ' \ 'SELECT ' \ - 'SUM(CASE WHEN raG.media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ - 'SUM(CASE WHEN NOT raG.grandparent_rating_key = "" THEN 1 ELSE 0 END) AS tv_count ' \ + '0 AS movie_count, ' \ + 'SUM(CASE WHEN NOT raG.grandparent_rating_key = "" THEN 1 ELSE 0 END) AS tv_count, ' \ + '0 AS season_count, ' \ + '0 AS episode_count ' \ 'FROM (SELECT * ' \ ' FROM recently_added ' \ - ' WHERE NOT grandparent_rating_key = "" AND added_at >= %s '\ - ' GROUP BY grandparent_rating_key) AS raG ' % (timestamp, timestamp) + ' WHERE NOT grandparent_rating_key = "" AND added_at >= %s ' \ + ' GROUP BY grandparent_rating_key) AS raG ' \ + 'UNION ALL ' \ + 'SELECT ' \ + '0 AS movie_count, ' \ + '0 AS tv_count, ' \ + 'SUM(CASE WHEN NOT raS.parent_rating_key = "" THEN 1 ELSE 0 END) AS season_count, ' \ + '0 AS episode_count ' \ + 'FROM (SELECT * ' \ + 'FROM recently_added ' \ + 'WHERE NOT parent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ + 'GROUP BY parent_rating_key) AS raS' % (timestamp, timestamp, timestamp) result = monitor_db.select(query) @@ -449,24 +463,46 @@ class Graphs(object): logger.warn("Tautulli Graphs :: Unable to execute database query for get_total_additions_by_media_type: %s." % e) return None - categories = ["Movies", "Shows"] + categories = ["Movies", "TV"] series_1 = [] series_2 = [] + series_3 = [] + series_4 = [] - for item in result: + _episodes = 0 + + for idx, item in enumerate(result): + if idx == 2: + series_3[1] = item['season_count'] + continue series_1.append(item['movie_count']) series_2.append(item['tv_count']) + series_3.append(item['season_count']) + #Value switch implemented as the episode count comes with the first item which is for the movies category, + # but needs to be placed at the second item for the TV category + if item['episode_count'] == 0: + series_4.append(_episodes) + else: + if item['tv_count'] != None: + series_4.append(0) + _episodes = item['episode_count'] series_1_output = {'name': 'Movies', 'data': series_1} series_2_output = {'name': 'Shows', 'data': series_2} + series_3_output = {'name': 'Seasons', + 'data': series_3} + series_4_output = {'name': 'Episodes', + 'data': series_4} series_output = [] if libraries.has_library_type('movie'): series_output.append(series_1_output) if libraries.has_library_type('show'): series_output.append(series_2_output) + series_output.append(series_3_output) + series_output.append(series_4_output) output = {'categories': categories, 'series': series_output} From 0f9692426e7961c81d2b32e887d0c25e3636decc Mon Sep 17 00:00:00 2001 From: herby2212 Date: Tue, 20 Jul 2021 19:32:02 +0200 Subject: [PATCH 03/31] fix show count including tracks --- data/interfaces/default/graphs.html | 6 +++--- plexpy/graphs.py | 9 +++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index c45de27a..e690960e 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -322,9 +322,9 @@ function getGraphColors(data_series) { var colors = { - 'Shows': '#669900', - 'Seasons': '#99cc00', - 'Episodes': '#cccc00', + 'Shows': '#ff3300', + 'Seasons': '#ff6600', + 'Episodes': '#ff9933', 'TV': '#E5A00D', 'Movies': '#FFFFFF', 'Music': '#F06464', diff --git a/plexpy/graphs.py b/plexpy/graphs.py index 7ce2858c..d4779867 100644 --- a/plexpy/graphs.py +++ b/plexpy/graphs.py @@ -444,7 +444,7 @@ class Graphs(object): '0 AS episode_count ' \ 'FROM (SELECT * ' \ ' FROM recently_added ' \ - ' WHERE NOT grandparent_rating_key = "" AND added_at >= %s ' \ + ' WHERE NOT grandparent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ ' GROUP BY grandparent_rating_key) AS raG ' \ 'UNION ALL ' \ 'SELECT ' \ @@ -473,8 +473,13 @@ class Graphs(object): for idx, item in enumerate(result): if idx == 2: - series_3[1] = item['season_count'] + series_3[1] = item['season_count'] if item['season_count'] is not None else 0 continue + + item['movie_count'] = 0 if item['movie_count'] is None else item['movie_count'] + item['tv_count'] = 0 if item['tv_count'] is None else item['tv_count'] + item['season_count'] = 0 if item['season_count'] is None else item['season_count'] + series_1.append(item['movie_count']) series_2.append(item['tv_count']) series_3.append(item['season_count']) From 01bce8ba31095b207d6c471588e63973b2ea5437 Mon Sep 17 00:00:00 2001 From: herby2212 Date: Tue, 20 Jul 2021 20:34:02 +0200 Subject: [PATCH 04/31] Library Statistics tab and move + new graph --- data/interfaces/default/graphs.html | 130 ++++++++++++++---- .../default/js/graphs/additions_by_day.js | 71 ++++++++++ 2 files changed, 172 insertions(+), 29 deletions(-) create mode 100644 data/interfaces/default/js/graphs/additions_by_day.js diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index e690960e..56b7da08 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -45,6 +45,7 @@
  • Media Type
  • Stream Type
  • Play Totals
  • +
  • Library Statistics
  • @@ -122,22 +123,6 @@
    -
    -
    -

    Addition count by media type Last 30 days

    -

    - The combined total of tv, movies, and music added to the server. -

    -
    -
    -
    - Loading chart... -
    -
    -
    -
    -
    -
    @@ -228,6 +213,38 @@
    +
    +
    +
    +

    Daily addition by media type Last 30 days

    +

    + The total addition count of shows, seasons, episodes and movies per day. +

    +
    +
    +
    Loading chart...
    +
    +
    +
    +
    +
    +
    +
    +

    Addition count by media type Last 30 days

    +

    + The combined total of tv, movies, and music added to the server. +

    +
    +
    +
    + Loading chart... +
    +
    +
    +
    +
    +
    +
    @@ -341,6 +358,7 @@ } + @@ -372,6 +390,8 @@ case '#tabs-3': current_tab = '#tabs-total' break + case '#tabs-4': + current_tab = '#tabs-library-statistics' default: break } @@ -501,19 +521,6 @@ var hc_plays_by_user = new Highcharts.Chart(hc_plays_by_user_options); } }); - - $.ajax({ - url: "get_additions_by_media_type", - type: 'get', - data: { time_range: time_range }, - dataType: "json", - success: function(data) { - hc_additions_by_media_type_options.xAxis.categories = data.categories; - hc_additions_by_media_type_options.series = getGraphVisibility(hc_additions_by_media_type_options.chart.renderTo, data.series); - hc_additions_by_media_type_options.colors = getGraphColors(data.series); - var hc_additions_by_media_type = new Highcharts.Chart(hc_additions_by_media_type_options); - } - }); $('#nav-tabs-plays').tab('show'); } @@ -635,10 +642,61 @@ $('#nav-tabs-total').tab('show'); } + function loadGraphsTab4(time_range, yaxis) { + $('#days-selection').show(); + $('#months-selection').hide(); + + setGraphFormat(yaxis); + + $.ajax({ + url: "get_plays_by_date", + type: 'get', + data: { time_range: time_range, y_axis: yaxis, user_id: selected_user_id }, + dataType: "json", + success: function(data) { + var dateArray = []; + $.each(data.categories, function (i, day) { + dateArray.push(moment(day, 'YYYY-MM-DD').valueOf()); + // Highlight the weekend + if ((moment(day, 'YYYY-MM-DD').format('ddd') === 'Sat') || + (moment(day, 'YYYY-MM-DD').format('ddd') === 'Sun')) { + hc_plays_by_day_options.xAxis.plotBands.push({ + from: i-0.5, + to: i+0.5, + color: 'rgba(80,80,80,0.3)' + }); + } + }); + if (yaxis === 'duration') { dataSecondsToHours(data); } + hc_additions_by_day_options.yAxis.min = 0; + hc_additions_by_day_options.xAxis.categories = dateArray; + hc_additions_by_day_options.series = getGraphVisibility(hc_additions_by_day_options.chart.renderTo, data.series); + hc_additions_by_day_options.colors = getGraphColors(data.series); + var hc_additions_by_day = new Highcharts.Chart(hc_additions_by_day_options); + } + }); + + $.ajax({ + url: "get_additions_by_media_type", + type: 'get', + data: { time_range: time_range }, + dataType: "json", + success: function(data) { + hc_additions_by_media_type_options.xAxis.categories = data.categories; + hc_additions_by_media_type_options.series = getGraphVisibility(hc_additions_by_media_type_options.chart.renderTo, data.series); + hc_additions_by_media_type_options.colors = getGraphColors(data.series); + var hc_additions_by_media_type = new Highcharts.Chart(hc_additions_by_media_type_options); + } + }); + + $('#nav-tabs-library-statistics').tab('show') + } + // Set initial state if (current_tab === '#tabs-plays') { loadGraphsTab1(current_day_range, yaxis); } if (current_tab === '#tabs-stream') { loadGraphsTab2(current_day_range, yaxis); } if (current_tab === '#tabs-total') { loadGraphsTab3(current_month_range, yaxis); } + if (current_tab === '#tabs-library-statistics') { loadGraphsTab4(current_day_range, yaxis); } // Tab1 opened $('#nav-tabs-plays').on('shown.bs.tab', function (e) { @@ -664,6 +722,14 @@ loadGraphsTab3(current_month_range, yaxis); }); + // Tab4 opened + $('#nav-tabs-library-statistics').on('shown.bs.tab', function (e) { + e.preventDefault(); + current_tab = $(this).attr('href'); + setLocalStorage('graph_tab', current_tab.replace('#','')); + loadGraphsTab4(current_day_range, yaxis); + }); + // Date range changed $('#graph-days').tooltip({ container: 'body', placement: 'top', html: true }); $('#graph-days').on('change', function() { @@ -672,6 +738,7 @@ setLocalStorage('graph_days', current_day_range); if (current_tab === '#tabs-plays') { loadGraphsTab1(current_day_range, yaxis); } if (current_tab === '#tabs-stream') { loadGraphsTab2(current_day_range, yaxis); } + if (current_tab === '#tabs-library-statistics') { loadGraphsTab4(current_day_range, yaxis); } $('.days').html(current_day_range); }); @@ -691,6 +758,7 @@ if (current_tab === '#tabs-plays') { loadGraphsTab1(current_day_range, yaxis); } if (current_tab === '#tabs-stream') { loadGraphsTab2(current_day_range, yaxis); } if (current_tab === '#tabs-total') { loadGraphsTab3(current_month_range, yaxis); } + //GraphTab4 not needed as no user relevant graph is included here -> may change in the future? }); // Y-axis changed @@ -700,6 +768,7 @@ if (current_tab === '#tabs-plays') { loadGraphsTab1(current_day_range, yaxis); } if (current_tab === '#tabs-stream') { loadGraphsTab2(current_day_range, yaxis); } if (current_tab === '#tabs-total') { loadGraphsTab3(current_month_range, yaxis); } + //GraphTab4 not needed as Addition Count is used for the Y-Axis }); function setGraphFormat(type) { @@ -759,6 +828,7 @@ } hc_plays_by_day_options.xAxis.plotBands = []; + hc_additions_by_day_options.xAxis.plotBands = []; hc_plays_by_stream_type_options.xAxis.plotBands = []; hc_plays_by_day_options.yAxis.labels.formatter = yaxis_format; @@ -771,6 +841,7 @@ hc_plays_by_stream_resolution_options.yAxis.labels.formatter = yaxis_format; hc_plays_by_platform_by_stream_type_options.yAxis.labels.formatter = yaxis_format; hc_additions_by_media_type_options.yAxis.labels.formatter = yaxis_format; + hc_additions_by_day_options.yAxis.labels.formatter = yaxis_format; hc_plays_by_user_by_stream_type_options.yAxis.labels.formatter = yaxis_format; hc_plays_by_month_options.yAxis.labels.formatter = yaxis_format; @@ -779,6 +850,7 @@ hc_plays_by_hourofday_options.tooltip.formatter = tooltip_format; hc_plays_by_platform_options.tooltip.formatter = tooltip_format; hc_additions_by_media_type_options.tooltip.formatter = tooltip_format; + hc_additions_by_day_options.tooltip.formatter = tooltip_format; hc_plays_by_user_options.tooltip.formatter = tooltip_format; hc_plays_by_stream_type_options.tooltip.formatter = tooltip_format; hc_plays_by_source_resolution_options.tooltip.formatter = tooltip_format; diff --git a/data/interfaces/default/js/graphs/additions_by_day.js b/data/interfaces/default/js/graphs/additions_by_day.js new file mode 100644 index 00000000..2ab12e3a --- /dev/null +++ b/data/interfaces/default/js/graphs/additions_by_day.js @@ -0,0 +1,71 @@ +var hc_additions_by_day_options = { + chart: { + type: 'line', + backgroundColor: 'rgba(0,0,0,0)', + renderTo: 'graph_additions_by_day' + }, + title: { + text: '' + }, + legend: { + enabled: true, + itemStyle: { + font: '9pt "Open Sans", sans-serif', + color: '#A0A0A0' + }, + itemHoverStyle: { + color: '#FFF' + }, + itemHiddenStyle: { + color: '#444' + } + }, + credits: { + enabled: false + }, + plotOptions: { + series: { + cursor: 'pointer', + point: { + events: { + click: function () { + selectHandler(this.category, this.series.name); + } + } + }, + events: { + legendItemClick: function() { + setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name); + } + } + } + }, + xAxis: { + type: 'datetime', + labels: { + formatter: function() { + return moment(this.value).format("MMM D"); + }, + style: { + color: '#aaa' + } + }, + categories: [{}], + plotBands: [] + }, + yAxis: { + title: { + text: null + }, + labels: { + style: { + color: '#aaa' + } + } + }, + tooltip: { + shared: true, + crosshairs: true + }, + series: [{}] +}; \ No newline at end of file From 0c5832bd4bb236371510785f4382e536b1bda244 Mon Sep 17 00:00:00 2001 From: herby2212 Date: Tue, 20 Jul 2021 22:22:35 +0200 Subject: [PATCH 05/31] Daily addition by media type graph --- data/interfaces/default/graphs.html | 4 +- plexpy/graphs.py | 99 +++++++++++++++++++++++++++++ plexpy/webserve.py | 14 ++++ 3 files changed, 115 insertions(+), 2 deletions(-) diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index 56b7da08..c4e45ecd 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -649,9 +649,9 @@ setGraphFormat(yaxis); $.ajax({ - url: "get_plays_by_date", + url: "get_additions_by_date", type: 'get', - data: { time_range: time_range, y_axis: yaxis, user_id: selected_user_id }, + data: { time_range: time_range }, dataType: "json", success: function(data) { var dateArray = []; diff --git a/plexpy/graphs.py b/plexpy/graphs.py index d4779867..886594ea 100644 --- a/plexpy/graphs.py +++ b/plexpy/graphs.py @@ -421,6 +421,105 @@ class Graphs(object): 'series': series_output} return output + def get_total_additions_per_day(self, time_range='30'): + monitor_db = database.MonitorDatabase() + + time_range = helpers.cast_to_int(time_range) or 30 + timestamp = helpers.timestamp() - time_range * 24 * 60 * 60 + + try: + query = 'SELECT raM.date_added, ' \ + 'SUM(CASE WHEN raM.media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ + '0 AS tv_count, ' \ + '0 AS season_count, ' \ + 'SUM(CASE WHEN raM.media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ + 'FROM (SELECT *, date(added_at, "unixepoch", "localtime") AS date_added ' \ + ' FROM recently_added ' \ + ' WHERE (media_type = "movie" OR media_type = "episode") AND added_at >= %s) AS raM ' \ + 'GROUP BY raM.date_added ' \ + 'UNION ALL ' \ + 'SELECT raG.date_added, ' \ + '0 AS movie_count, ' \ + 'SUM(CASE WHEN NOT raG.grandparent_rating_key = "" THEN 1 ELSE 0 END) AS tv_count, ' \ + '0 AS season_count, ' \ + '0 AS episode_count ' \ + 'FROM (SELECT *, date(added_at, "unixepoch", "localtime") AS date_added ' \ + ' FROM recently_added ' \ + ' WHERE NOT grandparent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ + ' GROUP BY grandparent_rating_key) AS raG ' \ + 'GROUP BY raG.date_added ' \ + 'UNION ALL ' \ + 'SELECT raS.date_added, ' \ + '0 AS movie_count, ' \ + '0 AS tv_count, ' \ + 'SUM(CASE WHEN NOT raS.parent_rating_key = "" THEN 1 ELSE 0 END) AS season_count, ' \ + '0 AS episode_count ' \ + 'FROM (SELECT *, date(added_at, "unixepoch", "localtime") AS date_added ' \ + ' FROM recently_added ' \ + ' WHERE NOT parent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ + ' GROUP BY parent_rating_key) AS raS ' \ + 'GROUP BY raS.date_added ' \ + 'ORDER BY date_added' % (timestamp, timestamp, timestamp) + + result = monitor_db.select(query) + + except Exception as e: + logger.warn("Tautulli Graphs :: Unable to execute database query for get_total_additions_per_day: %s." % e) + return None + + # create our date range as some days may not have any data + # but we still want to display them + base = datetime.date.today() + date_list = [base - datetime.timedelta(days=x) for x in range(0, int(time_range))] + + categories = [] + series_1 = [] + series_2 = [] + series_3 = [] + series_4 = [] + + for date_item in sorted(date_list): + date_string = date_item.strftime('%Y-%m-%d') + categories.append(date_string) + series_1_value = 0 + series_2_value = 0 + series_3_value = 0 + series_4_value = 0 + for item in result: + if date_string == item['date_added']: + series_1_value = item['movie_count'] if series_1_value is 0 else series_1_value + series_2_value = item['tv_count'] if series_2_value is 0 else series_2_value + series_3_value = item['season_count'] if series_3_value is 0 else series_3_value + series_4_value = item['episode_count'] if series_4_value is 0 else series_4_value + continue + + series_1.append(series_1_value) + series_2.append(series_2_value) + series_3.append(series_3_value) + series_4.append(series_4_value) + + series_1_output = {'name': 'Movies', + 'data': series_1} + series_2_output = {'name': 'Shows', + 'data': series_2} + series_3_output = {'name': 'Seasons', + 'data': series_3} + series_4_output = {'name': 'Episodes', + 'data': series_4} + + series_output = [] + if libraries.has_library_type('movie'): + series_output.append(series_1_output) + if libraries.has_library_type('show'): + series_output.append(series_2_output) + series_output.append(series_3_output) + series_output.append(series_4_output) + + output = {'categories': categories, + 'series': series_output} + + return output + def get_total_additions_by_media_type(self, time_range='30'): monitor_db = database.MonitorDatabase() diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 9399f045..55dfb5b6 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -2473,6 +2473,20 @@ class WebInterface(object): logger.warn("Unable to retrieve data for get_additions_by_media_type.") return result + @cherrypy.expose + @cherrypy.tools.json_out() + @requireAuth() + #called additions instead of adds so it isn't blocked by adblockers... + def get_additions_by_date(self, time_range='30'): + graph = graphs.Graphs() + result = graph.get_total_additions_per_day(time_range=time_range) + + if result: + return result + else: + logger.warn("Unable to retrieve data for get_additions_by_date.") + return result + @cherrypy.expose @cherrypy.tools.json_out() @requireAuth() From 8ce93697c4ee16822e4d6d9b6c8612171f47ca7c Mon Sep 17 00:00:00 2001 From: herby2212 Date: Wed, 21 Jul 2021 16:48:12 +0200 Subject: [PATCH 06/31] fixed highlighting of weekends --- data/interfaces/default/graphs.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index c4e45ecd..37d940d7 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -660,7 +660,7 @@ // Highlight the weekend if ((moment(day, 'YYYY-MM-DD').format('ddd') === 'Sat') || (moment(day, 'YYYY-MM-DD').format('ddd') === 'Sun')) { - hc_plays_by_day_options.xAxis.plotBands.push({ + hc_additions_by_day_options.xAxis.plotBands.push({ from: i-0.5, to: i+0.5, color: 'rgba(80,80,80,0.3)' From 1811e879cece4ec885c6ad6fa6a39bbac5799ea8 Mon Sep 17 00:00:00 2001 From: herby2212 Date: Fri, 23 Jul 2021 17:12:49 +0200 Subject: [PATCH 07/31] move episode count on second yAxis --- data/interfaces/default/graphs.html | 8 ++++++-- .../default/js/graphs/additions_by_day.js | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index 37d940d7..bbff2bcc 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -322,6 +322,10 @@ return data_series.map(function(s) { var obj = Object.assign({}, s); + if(obj.name == "Episodes" && chart_name == "graph_additions_by_day") { + obj.yAxis = 1; + obj.softThreshold = true; + } obj.visible = (chart_visibility[s.name] !== false); return obj }); @@ -667,7 +671,6 @@ }); } }); - if (yaxis === 'duration') { dataSecondsToHours(data); } hc_additions_by_day_options.yAxis.min = 0; hc_additions_by_day_options.xAxis.categories = dateArray; hc_additions_by_day_options.series = getGraphVisibility(hc_additions_by_day_options.chart.renderTo, data.series); @@ -840,8 +843,9 @@ hc_plays_by_source_resolution_options.yAxis.labels.formatter = yaxis_format; hc_plays_by_stream_resolution_options.yAxis.labels.formatter = yaxis_format; hc_plays_by_platform_by_stream_type_options.yAxis.labels.formatter = yaxis_format; + hc_additions_by_day_options.yAxis[0].labels.formatter = yaxis_format; + hc_additions_by_day_options.yAxis[1].labels.formatter = yaxis_format; hc_additions_by_media_type_options.yAxis.labels.formatter = yaxis_format; - hc_additions_by_day_options.yAxis.labels.formatter = yaxis_format; hc_plays_by_user_by_stream_type_options.yAxis.labels.formatter = yaxis_format; hc_plays_by_month_options.yAxis.labels.formatter = yaxis_format; diff --git a/data/interfaces/default/js/graphs/additions_by_day.js b/data/interfaces/default/js/graphs/additions_by_day.js index 2ab12e3a..3b97d053 100644 --- a/data/interfaces/default/js/graphs/additions_by_day.js +++ b/data/interfaces/default/js/graphs/additions_by_day.js @@ -25,6 +25,7 @@ var hc_additions_by_day_options = { }, plotOptions: { series: { + threshold: 0, cursor: 'pointer', point: { events: { @@ -53,7 +54,7 @@ var hc_additions_by_day_options = { categories: [{}], plotBands: [] }, - yAxis: { + yAxis: [{ title: { text: null }, @@ -62,7 +63,17 @@ var hc_additions_by_day_options = { color: '#aaa' } } - }, + }, { + title: { + text: null + }, + labels: { + style: { + color: '#aaa' + } + }, + opposite: true + }], tooltip: { shared: true, crosshairs: true From 2a7a211ad7860f299768cca2bd8dcc787e1c2ce1 Mon Sep 17 00:00:00 2001 From: herby2212 Date: Sun, 1 Aug 2021 18:25:43 +0200 Subject: [PATCH 08/31] addition count by source resolution graph --- data/interfaces/default/graphs.html | 30 +++++ .../graphs/additions_by_source_resolution.js | 73 ++++++++++++ plexpy/graphs.py | 108 ++++++++++++++++++ plexpy/webserve.py | 14 +++ 4 files changed, 225 insertions(+) create mode 100644 data/interfaces/default/js/graphs/additions_by_source_resolution.js diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index bbff2bcc..69b4cc80 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -243,6 +243,20 @@ +
    +

    Addition count by source resolution Last 30 days

    +

    + The combined total of tv, movies, and music added to the server by source resolution. +

    +
    +
    +
    + Loading chart... +
    +
    +
    +
    +
    @@ -362,6 +376,7 @@ } + @@ -692,6 +707,19 @@ } }); + $.ajax({ + url: "get_additions_by_resolution", + type: 'get', + data: { time_range: time_range }, + dataType: "json", + success: function(data) { + hc_additions_by_source_resolution_options.xAxis.categories = data.categories; + hc_additions_by_source_resolution_options.series = getGraphVisibility(hc_additions_by_source_resolution_options.chart.renderTo, data.series); + hc_additions_by_source_resolution_options.colors = getGraphColors(data.series); + var hc_additions_by_source_resolution = new Highcharts.Chart(hc_additions_by_source_resolution_options); + } + }); + $('#nav-tabs-library-statistics').tab('show') } @@ -846,6 +874,7 @@ hc_additions_by_day_options.yAxis[0].labels.formatter = yaxis_format; hc_additions_by_day_options.yAxis[1].labels.formatter = yaxis_format; hc_additions_by_media_type_options.yAxis.labels.formatter = yaxis_format; + hc_additions_by_source_resolution_options.yAxis.labels.formatter = yaxis_format; hc_plays_by_user_by_stream_type_options.yAxis.labels.formatter = yaxis_format; hc_plays_by_month_options.yAxis.labels.formatter = yaxis_format; @@ -854,6 +883,7 @@ hc_plays_by_hourofday_options.tooltip.formatter = tooltip_format; hc_plays_by_platform_options.tooltip.formatter = tooltip_format; hc_additions_by_media_type_options.tooltip.formatter = tooltip_format; + hc_additions_by_source_resolution_options.tooltip.formatter = tooltip_format; hc_additions_by_day_options.tooltip.formatter = tooltip_format; hc_plays_by_user_options.tooltip.formatter = tooltip_format; hc_plays_by_stream_type_options.tooltip.formatter = tooltip_format; diff --git a/data/interfaces/default/js/graphs/additions_by_source_resolution.js b/data/interfaces/default/js/graphs/additions_by_source_resolution.js new file mode 100644 index 00000000..25dcfaaa --- /dev/null +++ b/data/interfaces/default/js/graphs/additions_by_source_resolution.js @@ -0,0 +1,73 @@ +var hc_additions_by_source_resolution_options = { + chart: { + type: 'column', + backgroundColor: 'rgba(0,0,0,0)', + renderTo: 'graph_additions_by_source_resolution' + }, + title: { + text: '' + }, + legend: { + enabled: true, + itemStyle: { + font: '9pt "Open Sans", sans-serif', + color: '#A0A0A0' + }, + itemHoverStyle: { + color: '#FFF' + }, + itemHiddenStyle: { + color: '#444' + } + }, + credits: { + enabled: false + }, + xAxis: { + categories: [{}], + labels: { + style: { + color: '#aaa' + } + } + }, + yAxis: { + title: { + text: null + }, + labels: { + style: { + color: '#aaa' + } + }, + stackLabels: { + enabled: false, + style: { + color: '#fff' + } + } + }, + plotOptions: { + column: { + borderWidth: 0, + stacking: 'normal', + dataLabels: { + enabled: false, + style: { + color: '#000' + } + } + }, + series: { + events: { + legendItemClick: function () { + setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name); + } + } + } + }, + tooltip: { + shared: true + }, + series: [{}] +}; \ No newline at end of file diff --git a/plexpy/graphs.py b/plexpy/graphs.py index 886594ea..63c49efa 100644 --- a/plexpy/graphs.py +++ b/plexpy/graphs.py @@ -612,6 +612,114 @@ class Graphs(object): 'series': series_output} return output + def get_total_additions_by_resolution(self, time_range='30'): + monitor_db = database.MonitorDatabase() + + time_range = helpers.cast_to_int(time_range) or 30 + timestamp = helpers.timestamp() - time_range * 24 * 60 * 60 + + fourK = '\'%"video_resolution": "4K"%\'' + fullHD = '\'%"video_resolution": "1080"%\'' + hD = '\'%"video_resolution": "720"%\'' + sd = '\'%"video_resolution": "sd"%\'' + + try: + query = 'SELECT ra.resolution, SUM(ra.movie_count) AS movie_count, ' \ + 'SUM(ra.tv_count) AS tv_count, SUM(ra.season_count) AS season_count, SUM(ra.episode_count) AS episode_count ' \ + 'FROM(' \ + 'SELECT ' \ + 'raM.resolution, ' \ + 'SUM(CASE WHEN raM.media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ + '0 AS tv_count, ' \ + '0 AS season_count, ' \ + 'SUM(CASE WHEN raM.media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ + 'FROM (SELECT *, (' \ + 'CASE WHEN media_info LIKE %s THEN "4k" ' \ + 'WHEN media_info LIKE %s THEN "1080" ' \ + 'WHEN media_info LIKE %s THEN "720" ' \ + 'WHEN media_info LIKE %s THEN "SD" ELSE "Unknown" END) AS resolution ' \ + 'FROM recently_added ' \ + 'WHERE (media_type = "movie" OR media_type = "episode") AND added_at >= %s) AS raM ' \ + 'GROUP BY raM.resolution ' \ + 'UNION ALL ' \ + 'SELECT ' \ + 'raG.resolution, ' \ + '0 AS movie_count, ' \ + 'SUM(CASE WHEN NOT raG.grandparent_rating_key = "" THEN 1 ELSE 0 END) AS tv_count, ' \ + '0 AS season_count, ' \ + '0 AS episode_count ' \ + 'FROM (SELECT *, (' \ + 'CASE WHEN media_info LIKE %s THEN "4k" ' \ + 'WHEN media_info LIKE %s THEN "1080" ' \ + 'WHEN media_info LIKE %s THEN "720" ' \ + 'WHEN media_info LIKE %s THEN "SD" ELSE "Unknown" END) AS resolution ' \ + ' FROM recently_added ' \ + ' WHERE NOT grandparent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ + ' GROUP BY grandparent_rating_key) AS raG ' \ + 'GROUP BY raG.resolution ' \ + 'UNION ALL ' \ + 'SELECT ' \ + 'raS.resolution, ' \ + '0 AS movie_count, ' \ + '0 AS tv_count, ' \ + 'SUM(CASE WHEN NOT raS.parent_rating_key = "" THEN 1 ELSE 0 END) AS season_count, ' \ + '0 AS episode_count ' \ + 'FROM (SELECT *, (' \ + 'CASE WHEN media_info LIKE %s THEN "4k" ' \ + 'WHEN media_info LIKE %s THEN "1080" ' \ + 'WHEN media_info LIKE %s THEN "720" ' \ + 'WHEN media_info LIKE %s THEN "SD" ELSE "Unknown" END) AS resolution ' \ + 'FROM recently_added ' \ + 'WHERE NOT parent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ + 'GROUP BY parent_rating_key) AS raS ' \ + 'GROUP BY raS.resolution) AS ra ' \ + 'GROUP BY resolution ' \ + 'ORDER BY resolution' % (fourK, fullHD, hD, sd, timestamp, + fourK, fullHD, hD, sd, timestamp, + fourK, fullHD, hD, sd, timestamp) + + result = monitor_db.select(query) + + except Exception as e: + logger.warn("Tautulli Graphs :: Unable to execute database query for get_total_additions_by_media_type: %s." % e) + return None + + categories = [] + series_1 = [] + series_2 = [] + series_3 = [] + series_4 = [] + + _episodes = 0 + + for idx, item in enumerate(result): + categories.append(item['resolution']) + + series_1.append(item['movie_count']) + series_2.append(item['tv_count']) + series_3.append(item['season_count']) + series_4.append(item['episode_count']) + + series_1_output = {'name': 'Movies', + 'data': series_1} + series_2_output = {'name': 'Shows', + 'data': series_2} + series_3_output = {'name': 'Seasons', + 'data': series_3} + series_4_output = {'name': 'Episodes', + 'data': series_4} + + series_output = [] + if libraries.has_library_type('movie'): + series_output.append(series_1_output) + if libraries.has_library_type('show'): + series_output.append(series_2_output) + series_output.append(series_3_output) + series_output.append(series_4_output) + + output = {'categories': categories, + 'series': series_output} + return output def get_total_plays_per_month(self, time_range='12', y_axis='plays', user_id=None, grouping=None): monitor_db = database.MonitorDatabase() diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 55dfb5b6..8cb580e3 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -2473,6 +2473,20 @@ class WebInterface(object): logger.warn("Unable to retrieve data for get_additions_by_media_type.") return result + @cherrypy.expose + @cherrypy.tools.json_out() + @requireAuth() + #called additions instead of adds so it isn't blocked by adblockers... + def get_additions_by_resolution(self, time_range='30'): + graph = graphs.Graphs() + result = graph.get_total_additions_by_resolution(time_range=time_range) + + if result: + return result + else: + logger.warn("Unable to retrieve data for get_additions_by_resolution.") + return result + @cherrypy.expose @cherrypy.tools.json_out() @requireAuth() From a524630ba71953cb0f1909883db174905c17ed9b Mon Sep 17 00:00:00 2001 From: herby2212 Date: Sun, 1 Aug 2021 18:40:02 +0200 Subject: [PATCH 09/31] optimizing query with resolution_identifier --- plexpy/graphs.py | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/plexpy/graphs.py b/plexpy/graphs.py index 63c49efa..631bc22a 100644 --- a/plexpy/graphs.py +++ b/plexpy/graphs.py @@ -618,10 +618,10 @@ class Graphs(object): time_range = helpers.cast_to_int(time_range) or 30 timestamp = helpers.timestamp() - time_range * 24 * 60 * 60 - fourK = '\'%"video_resolution": "4K"%\'' - fullHD = '\'%"video_resolution": "1080"%\'' - hD = '\'%"video_resolution": "720"%\'' - sd = '\'%"video_resolution": "sd"%\'' + resolution_identifier = '(CASE WHEN media_info LIKE \'%"video_resolution": "4K"%\' THEN "4k" ' \ + 'WHEN media_info LIKE \'%"video_resolution": "1080"%\' THEN "1080" ' \ + 'WHEN media_info LIKE \'%"video_resolution": "720"%\' THEN "720" ' \ + 'WHEN media_info LIKE \'%"video_resolution": "sd"%\' THEN "SD" ELSE "Unknown" END) AS resolution ' try: query = 'SELECT ra.resolution, SUM(ra.movie_count) AS movie_count, ' \ @@ -633,11 +633,7 @@ class Graphs(object): '0 AS tv_count, ' \ '0 AS season_count, ' \ 'SUM(CASE WHEN raM.media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ - 'FROM (SELECT *, (' \ - 'CASE WHEN media_info LIKE %s THEN "4k" ' \ - 'WHEN media_info LIKE %s THEN "1080" ' \ - 'WHEN media_info LIKE %s THEN "720" ' \ - 'WHEN media_info LIKE %s THEN "SD" ELSE "Unknown" END) AS resolution ' \ + 'FROM (SELECT *, %s ' \ 'FROM recently_added ' \ 'WHERE (media_type = "movie" OR media_type = "episode") AND added_at >= %s) AS raM ' \ 'GROUP BY raM.resolution ' \ @@ -648,12 +644,8 @@ class Graphs(object): 'SUM(CASE WHEN NOT raG.grandparent_rating_key = "" THEN 1 ELSE 0 END) AS tv_count, ' \ '0 AS season_count, ' \ '0 AS episode_count ' \ - 'FROM (SELECT *, (' \ - 'CASE WHEN media_info LIKE %s THEN "4k" ' \ - 'WHEN media_info LIKE %s THEN "1080" ' \ - 'WHEN media_info LIKE %s THEN "720" ' \ - 'WHEN media_info LIKE %s THEN "SD" ELSE "Unknown" END) AS resolution ' \ - ' FROM recently_added ' \ + 'FROM (SELECT *, %s ' \ + 'FROM recently_added ' \ ' WHERE NOT grandparent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ ' GROUP BY grandparent_rating_key) AS raG ' \ 'GROUP BY raG.resolution ' \ @@ -664,24 +656,20 @@ class Graphs(object): '0 AS tv_count, ' \ 'SUM(CASE WHEN NOT raS.parent_rating_key = "" THEN 1 ELSE 0 END) AS season_count, ' \ '0 AS episode_count ' \ - 'FROM (SELECT *, (' \ - 'CASE WHEN media_info LIKE %s THEN "4k" ' \ - 'WHEN media_info LIKE %s THEN "1080" ' \ - 'WHEN media_info LIKE %s THEN "720" ' \ - 'WHEN media_info LIKE %s THEN "SD" ELSE "Unknown" END) AS resolution ' \ + 'FROM (SELECT *, %s ' \ 'FROM recently_added ' \ 'WHERE NOT parent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ 'GROUP BY parent_rating_key) AS raS ' \ 'GROUP BY raS.resolution) AS ra ' \ 'GROUP BY resolution ' \ - 'ORDER BY resolution' % (fourK, fullHD, hD, sd, timestamp, - fourK, fullHD, hD, sd, timestamp, - fourK, fullHD, hD, sd, timestamp) + 'ORDER BY resolution' % (resolution_identifier, timestamp, + resolution_identifier, timestamp, + resolution_identifier, timestamp) result = monitor_db.select(query) except Exception as e: - logger.warn("Tautulli Graphs :: Unable to execute database query for get_total_additions_by_media_type: %s." % e) + logger.warn("Tautulli Graphs :: Unable to execute database query for get_total_additions_by_resolution: %s." % e) return None categories = [] From 039bead3b0961d7af2c6d3a83e497657ddde7d00 Mon Sep 17 00:00:00 2001 From: herby2212 Date: Sun, 1 Aug 2021 20:41:39 +0200 Subject: [PATCH 10/31] optimized query and fixed sorting for ByResolution --- plexpy/graphs.py | 114 ++++++++++++++++++++++++----------------------- 1 file changed, 58 insertions(+), 56 deletions(-) diff --git a/plexpy/graphs.py b/plexpy/graphs.py index 631bc22a..16569449 100644 --- a/plexpy/graphs.py +++ b/plexpy/graphs.py @@ -528,33 +528,40 @@ class Graphs(object): try: query = 'SELECT ' \ - 'SUM(CASE WHEN raM.media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ - '0 AS tv_count, ' \ - '0 AS season_count, ' \ - 'SUM(CASE WHEN raM.media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ - 'FROM (SELECT * ' \ - ' FROM recently_added ' \ - ' WHERE (media_type = "movie" OR media_type = "episode") AND added_at >= %s) AS raM ' \ - 'UNION ALL ' \ - 'SELECT ' \ - '0 AS movie_count, ' \ - 'SUM(CASE WHEN NOT raG.grandparent_rating_key = "" THEN 1 ELSE 0 END) AS tv_count, ' \ - '0 AS season_count, ' \ - '0 AS episode_count ' \ - 'FROM (SELECT * ' \ - ' FROM recently_added ' \ - ' WHERE NOT grandparent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ - ' GROUP BY grandparent_rating_key) AS raG ' \ - 'UNION ALL ' \ - 'SELECT ' \ - '0 AS movie_count, ' \ - '0 AS tv_count, ' \ - 'SUM(CASE WHEN NOT raS.parent_rating_key = "" THEN 1 ELSE 0 END) AS season_count, ' \ - '0 AS episode_count ' \ - 'FROM (SELECT * ' \ - 'FROM recently_added ' \ - 'WHERE NOT parent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ - 'GROUP BY parent_rating_key) AS raS' % (timestamp, timestamp, timestamp) + 'SUM(ra.movie_count) AS movie_count, ' \ + 'SUM(ra.tv_count) AS tv_count, ' \ + 'SUM(ra.season_count) AS season_count, ' \ + 'SUM(ra.episode_count) AS episode_count ' \ + 'FROM (' \ + 'SELECT ' \ + 'SUM(CASE WHEN raM.media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ + '0 AS tv_count, ' \ + '0 AS season_count, ' \ + 'SUM(CASE WHEN raM.media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ + 'FROM (SELECT * ' \ + 'FROM recently_added ' \ + 'WHERE (media_type = "movie" OR media_type = "episode") AND added_at >= %s) AS raM ' \ + 'UNION ALL ' \ + 'SELECT ' \ + '0 AS movie_count, ' \ + 'SUM(CASE WHEN NOT raG.grandparent_rating_key = "" THEN 1 ELSE 0 END) AS tv_count, ' \ + '0 AS season_count, ' \ + '0 AS episode_count ' \ + 'FROM (SELECT * ' \ + 'FROM recently_added ' \ + 'WHERE NOT grandparent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ + 'GROUP BY grandparent_rating_key) AS raG ' \ + 'UNION ALL ' \ + 'SELECT ' \ + '0 AS movie_count, ' \ + '0 AS tv_count, ' \ + 'SUM(CASE WHEN NOT raS.parent_rating_key = "" THEN 1 ELSE 0 END) AS season_count, ' \ + '0 AS episode_count ' \ + 'FROM (SELECT * ' \ + 'FROM recently_added ' \ + 'WHERE NOT parent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ + 'GROUP BY parent_rating_key) AS raS ' \ + ') AS ra' % (timestamp, timestamp, timestamp) result = monitor_db.select(query) @@ -568,28 +575,19 @@ class Graphs(object): series_3 = [] series_4 = [] - _episodes = 0 + content = result[0] - for idx, item in enumerate(result): - if idx == 2: - series_3[1] = item['season_count'] if item['season_count'] is not None else 0 - continue - - item['movie_count'] = 0 if item['movie_count'] is None else item['movie_count'] - item['tv_count'] = 0 if item['tv_count'] is None else item['tv_count'] - item['season_count'] = 0 if item['season_count'] is None else item['season_count'] - - series_1.append(item['movie_count']) - series_2.append(item['tv_count']) - series_3.append(item['season_count']) - #Value switch implemented as the episode count comes with the first item which is for the movies category, - # but needs to be placed at the second item for the TV category - if item['episode_count'] == 0: - series_4.append(_episodes) + for idx, item in enumerate(categories): + if idx == 0: + series_1.append(content['movie_count']) + series_2.append(None) + series_3.append(None) + series_4.append(None) else: - if item['tv_count'] != None: - series_4.append(0) - _episodes = item['episode_count'] + series_1.append(None) + series_2.append(content['tv_count']) + series_3.append(content['season_count']) + series_4.append(content['episode_count']) series_1_output = {'name': 'Movies', 'data': series_1} @@ -610,6 +608,7 @@ class Graphs(object): output = {'categories': categories, 'series': series_output} + return output def get_total_additions_by_resolution(self, time_range='30'): @@ -618,14 +617,18 @@ class Graphs(object): time_range = helpers.cast_to_int(time_range) or 30 timestamp = helpers.timestamp() - time_range * 24 * 60 * 60 - resolution_identifier = '(CASE WHEN media_info LIKE \'%"video_resolution": "4K"%\' THEN "4k" ' \ - 'WHEN media_info LIKE \'%"video_resolution": "1080"%\' THEN "1080" ' \ - 'WHEN media_info LIKE \'%"video_resolution": "720"%\' THEN "720" ' \ - 'WHEN media_info LIKE \'%"video_resolution": "sd"%\' THEN "SD" ELSE "Unknown" END) AS resolution ' + resolution_identifier = '(CASE WHEN media_info LIKE \'%"video_resolution": "4K"%\' THEN "1_4K" ' \ + 'WHEN media_info LIKE \'%"video_resolution": "1080"%\' THEN "2_1080" ' \ + 'WHEN media_info LIKE \'%"video_resolution": "720"%\' THEN "3_720" ' \ + 'WHEN media_info LIKE \'%"video_resolution": "sd"%\' THEN "4_SD" ELSE "5_Unknown" END) AS resolution ' try: - query = 'SELECT ra.resolution, SUM(ra.movie_count) AS movie_count, ' \ - 'SUM(ra.tv_count) AS tv_count, SUM(ra.season_count) AS season_count, SUM(ra.episode_count) AS episode_count ' \ + query = 'SELECT ' \ + 'ra.resolution, ' \ + 'SUM(ra.movie_count) AS movie_count, ' \ + 'SUM(ra.tv_count) AS tv_count, ' \ + 'SUM(ra.season_count) AS season_count, ' \ + 'SUM(ra.episode_count) AS episode_count ' \ 'FROM(' \ 'SELECT ' \ 'raM.resolution, ' \ @@ -678,10 +681,9 @@ class Graphs(object): series_3 = [] series_4 = [] - _episodes = 0 - for idx, item in enumerate(result): - categories.append(item['resolution']) + #remove sorting indicators (like 1_%) + categories.append(item['resolution'][2:]) series_1.append(item['movie_count']) series_2.append(item['tv_count']) From 65fcb9ab37b6a12d89967548b795c5a1a3eb07d6 Mon Sep 17 00:00:00 2001 From: herby2212 Date: Wed, 18 Aug 2021 17:23:17 +0200 Subject: [PATCH 11/31] query optimizing + library growth graph synced --- data/interfaces/default/graphs.html | 157 ++++++++++++++++-- .../default/js/graphs/additions_by_day.js | 2 +- .../js/graphs/library_growth_by_day.js | 83 +++++++++ plexpy/graphs.py | 114 ++++++------- 4 files changed, 285 insertions(+), 71 deletions(-) create mode 100644 data/interfaces/default/js/graphs/library_growth_by_day.js diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index 69b4cc80..3594522b 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -214,16 +214,32 @@
    -
    -
    -

    Daily addition by media type Last 30 days

    -

    - The total addition count of shows, seasons, episodes and movies per day. -

    -
    -
    -
    Loading chart...
    -
    +
    +
    +
    +

    Daily addition by media type Last 30 days

    +

    + The total addition count of shows, seasons, episodes and movies per day. +

    +
    +
    +
    Loading chart...
    +
    +
    +
    +
    +
    +
    +
    +

    Library Growth Last 30 days

    +

    + The overall library growth by the library stats per day. +

    +
    +
    +
    Loading chart...
    +
    +
    @@ -336,11 +352,12 @@ return data_series.map(function(s) { var obj = Object.assign({}, s); - if(obj.name == "Episodes" && chart_name == "graph_additions_by_day") { + obj.visible = (chart_visibility[s.name] !== false); + if(obj.name == "Episodes" && (chart_name == "graph_additions_by_day" || chart_name == "library_growth_by_day")) { obj.yAxis = 1; obj.softThreshold = true; - } - obj.visible = (chart_visibility[s.name] !== false); + //obj.chart.options.yAxis[1].title.text = (obj.visible) ? "Episodes" : ""; + } return obj }); } @@ -349,7 +366,13 @@ var chart_key = 'HighCharts_' + chart_name; var chart_visibility = data_series.map(function(s) { - return {name: s.name, visible: (s.name === series_name) ? !s.visible : s.visible} + var _visible = (s.name === series_name) ? !s.visible : s.visible; + if(s.name == "Episodes") { + document.getElementsByClassName("highcharts-yaxis-title")[0].innerHTML = (_visible) ? "Episodes" : ""; + } + return { + name: s.name, visible: _visible + } }); setLocalStorage(chart_key, JSON.stringify(chart_visibility)); @@ -376,6 +399,7 @@ } + @@ -684,6 +708,12 @@ to: i+0.5, color: 'rgba(80,80,80,0.3)' }); + + hc_library_growth_by_day_options.xAxis.plotBands.push({ + from: i-0.5, + to: i+0.5, + color: 'rgba(80,80,80,0.3)' + }); } }); hc_additions_by_day_options.yAxis.min = 0; @@ -691,6 +721,12 @@ hc_additions_by_day_options.series = getGraphVisibility(hc_additions_by_day_options.chart.renderTo, data.series); hc_additions_by_day_options.colors = getGraphColors(data.series); var hc_additions_by_day = new Highcharts.Chart(hc_additions_by_day_options); + + hc_library_growth_by_day_options.yAxis.min = 0; + hc_library_growth_by_day_options.xAxis.categories = dateArray; + hc_library_growth_by_day_options.series = getGraphVisibility(hc_library_growth_by_day_options.chart.renderTo, data.series); + hc_library_growth_by_day_options.colors = getGraphColors(data.series); + var hc_library_growth_by_day = new Highcharts.Chart(hc_library_growth_by_day_options); } }); @@ -860,6 +896,7 @@ hc_plays_by_day_options.xAxis.plotBands = []; hc_additions_by_day_options.xAxis.plotBands = []; + hc_library_growth_by_day_options.xAxis.plotBands = []; hc_plays_by_stream_type_options.xAxis.plotBands = []; hc_plays_by_day_options.yAxis.labels.formatter = yaxis_format; @@ -873,6 +910,8 @@ hc_plays_by_platform_by_stream_type_options.yAxis.labels.formatter = yaxis_format; hc_additions_by_day_options.yAxis[0].labels.formatter = yaxis_format; hc_additions_by_day_options.yAxis[1].labels.formatter = yaxis_format; + hc_library_growth_by_day_options.yAxis[0].labels.formatter = yaxis_format; + hc_library_growth_by_day_options.yAxis[1].labels.formatter = yaxis_format; hc_additions_by_media_type_options.yAxis.labels.formatter = yaxis_format; hc_additions_by_source_resolution_options.yAxis.labels.formatter = yaxis_format; hc_plays_by_user_by_stream_type_options.yAxis.labels.formatter = yaxis_format; @@ -885,6 +924,7 @@ hc_additions_by_media_type_options.tooltip.formatter = tooltip_format; hc_additions_by_source_resolution_options.tooltip.formatter = tooltip_format; hc_additions_by_day_options.tooltip.formatter = tooltip_format; + hc_library_growth_by_day_options.tooltip.formatter = tooltip_format; hc_plays_by_user_options.tooltip.formatter = tooltip_format; hc_plays_by_stream_type_options.tooltip.formatter = tooltip_format; hc_plays_by_source_resolution_options.tooltip.formatter = tooltip_format; @@ -897,5 +937,94 @@ hc_plays_by_month_options.yAxis.stackLabels.formatter = stack_labels_format; } }); + + ['mousemove', 'touchmove', 'touchstart'].forEach(function (eventType) { + document.getElementById('section-library-graphs').addEventListener( + eventType, + function (e) { + var chart, + point, + points, + i, + event; + + for (i = 0; i < Highcharts.charts.length; i = i + 1) { + chart = Highcharts.charts[i]; + if(chart.options.chart.type != "line") { + continue; + } + // Find coordinates within the chart + event = chart.pointer.normalize(e); + // Get the hovered point + points = []; + chart.series.forEach(function (series, idx) { + if(idx == 0) { + series.tooltipOptions.followPointer = false; + } + points.push(series.searchPoint(event, true)); + }); + //point = chart.series[0].searchPoint(event, true); + /* + if (point) { + points[2].highlight(e); + points[1].highlight(e); + points[0].highlight(e); + } + */ + if(!points.includes(undefined)) { + chart.tooltip.refresh(points); + chart.xAxis[0].drawCrosshair(e, points[0]); + chart.xAxis[0].drawCrosshair(e, points[points.length]); + } + } + } + ); + }); + + //https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/demo/synchronized-charts + Highcharts.Point.prototype.highlight = function (event) { + this.onMouseOver(); // Show the hover marker + var a = [this]; + //this.series.chart.tooltip.refresh(a); // Show the tooltip + this.series.chart.xAxis[0].drawCrosshair(event, this); // Show the crosshair + }; + + //MAYBE NOT EVEN NESSECARY + Highcharts.Pointer.prototype.reset = function (a, b) { + if(this.chart.renderTo.outerHTML.includes("library")) { + return undefined; + } + var c = this.chart + , d = c.hoverSeries + , e = c.hoverPoint + , f = c.hoverPoints + , g = c.tooltip + , h = g && g.shared ? f : e; + (a = a && g && h) && sa(h)[0].plotX === y && (a = !1); + if (a) + g.refresh(h), + e && (e.setState(e.state, !0), + Highcharts.each(c.axes, function(a) { + p(a.options.crosshair && a.options.crosshair.snap, !0) ? a.drawCrosshair(null, e) : a.hideCrosshair() + })); + else { + if (e) + e.onMouseOut(); + f && Highcharts.each(f, function(a) { + a.setState() + }); + if (d) + d.onMouseOut(); + g && g.hide(b); + if (this._onDocumentMouseMove) + Highcharts.removeEvent(document, "mousemove", this._onDocumentMouseMove), + this._onDocumentMouseMove = null; + Highcharts.each(c.axes, function(a) { + a.hideCrosshair() + }); + this.hoverX = c.hoverPoints = c.hoverPoint = null + } + }; + \ No newline at end of file diff --git a/data/interfaces/default/js/graphs/additions_by_day.js b/data/interfaces/default/js/graphs/additions_by_day.js index 3b97d053..a64a6acc 100644 --- a/data/interfaces/default/js/graphs/additions_by_day.js +++ b/data/interfaces/default/js/graphs/additions_by_day.js @@ -65,7 +65,7 @@ var hc_additions_by_day_options = { } }, { title: { - text: null + text: 'Episodes' }, labels: { style: { diff --git a/data/interfaces/default/js/graphs/library_growth_by_day.js b/data/interfaces/default/js/graphs/library_growth_by_day.js new file mode 100644 index 00000000..09fd0aa9 --- /dev/null +++ b/data/interfaces/default/js/graphs/library_growth_by_day.js @@ -0,0 +1,83 @@ +var hc_library_growth_by_day_options = { + chart: { + type: 'line', + backgroundColor: 'rgba(0,0,0,0)', + renderTo: 'library_growth_by_day' + }, + title: { + text: '' + }, + legend: { + enabled: true, + itemStyle: { + font: '9pt "Open Sans", sans-serif', + color: '#A0A0A0' + }, + itemHoverStyle: { + color: '#FFF' + }, + itemHiddenStyle: { + color: '#444' + } + }, + credits: { + enabled: false + }, + plotOptions: { + series: { + allowPointSelect: false, + threshold: 0, + cursor: 'pointer', + point: { + events: { + click: function () { + selectHandler(this.category, this.series.name); + } + } + }, + events: { + legendItemClick: function() { + setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name); + } + } + } + }, + xAxis: { + type: 'datetime', + labels: { + formatter: function() { + return moment(this.value).format("MMM D"); + }, + style: { + color: '#aaa' + } + }, + categories: [{}], + plotBands: [] + }, + yAxis: [{ + title: { + text: null + }, + labels: { + style: { + color: '#aaa' + } + } + }, { + title: { + text: 'Episodes' + }, + labels: { + style: { + color: '#aaa' + } + }, + opposite: true + }], + tooltip: { + shared: true, + crosshairs: true + }, + series: [{}] +}; \ No newline at end of file diff --git a/plexpy/graphs.py b/plexpy/graphs.py index 631bc22a..16569449 100644 --- a/plexpy/graphs.py +++ b/plexpy/graphs.py @@ -528,33 +528,40 @@ class Graphs(object): try: query = 'SELECT ' \ - 'SUM(CASE WHEN raM.media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ - '0 AS tv_count, ' \ - '0 AS season_count, ' \ - 'SUM(CASE WHEN raM.media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ - 'FROM (SELECT * ' \ - ' FROM recently_added ' \ - ' WHERE (media_type = "movie" OR media_type = "episode") AND added_at >= %s) AS raM ' \ - 'UNION ALL ' \ - 'SELECT ' \ - '0 AS movie_count, ' \ - 'SUM(CASE WHEN NOT raG.grandparent_rating_key = "" THEN 1 ELSE 0 END) AS tv_count, ' \ - '0 AS season_count, ' \ - '0 AS episode_count ' \ - 'FROM (SELECT * ' \ - ' FROM recently_added ' \ - ' WHERE NOT grandparent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ - ' GROUP BY grandparent_rating_key) AS raG ' \ - 'UNION ALL ' \ - 'SELECT ' \ - '0 AS movie_count, ' \ - '0 AS tv_count, ' \ - 'SUM(CASE WHEN NOT raS.parent_rating_key = "" THEN 1 ELSE 0 END) AS season_count, ' \ - '0 AS episode_count ' \ - 'FROM (SELECT * ' \ - 'FROM recently_added ' \ - 'WHERE NOT parent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ - 'GROUP BY parent_rating_key) AS raS' % (timestamp, timestamp, timestamp) + 'SUM(ra.movie_count) AS movie_count, ' \ + 'SUM(ra.tv_count) AS tv_count, ' \ + 'SUM(ra.season_count) AS season_count, ' \ + 'SUM(ra.episode_count) AS episode_count ' \ + 'FROM (' \ + 'SELECT ' \ + 'SUM(CASE WHEN raM.media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ + '0 AS tv_count, ' \ + '0 AS season_count, ' \ + 'SUM(CASE WHEN raM.media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ + 'FROM (SELECT * ' \ + 'FROM recently_added ' \ + 'WHERE (media_type = "movie" OR media_type = "episode") AND added_at >= %s) AS raM ' \ + 'UNION ALL ' \ + 'SELECT ' \ + '0 AS movie_count, ' \ + 'SUM(CASE WHEN NOT raG.grandparent_rating_key = "" THEN 1 ELSE 0 END) AS tv_count, ' \ + '0 AS season_count, ' \ + '0 AS episode_count ' \ + 'FROM (SELECT * ' \ + 'FROM recently_added ' \ + 'WHERE NOT grandparent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ + 'GROUP BY grandparent_rating_key) AS raG ' \ + 'UNION ALL ' \ + 'SELECT ' \ + '0 AS movie_count, ' \ + '0 AS tv_count, ' \ + 'SUM(CASE WHEN NOT raS.parent_rating_key = "" THEN 1 ELSE 0 END) AS season_count, ' \ + '0 AS episode_count ' \ + 'FROM (SELECT * ' \ + 'FROM recently_added ' \ + 'WHERE NOT parent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ + 'GROUP BY parent_rating_key) AS raS ' \ + ') AS ra' % (timestamp, timestamp, timestamp) result = monitor_db.select(query) @@ -568,28 +575,19 @@ class Graphs(object): series_3 = [] series_4 = [] - _episodes = 0 + content = result[0] - for idx, item in enumerate(result): - if idx == 2: - series_3[1] = item['season_count'] if item['season_count'] is not None else 0 - continue - - item['movie_count'] = 0 if item['movie_count'] is None else item['movie_count'] - item['tv_count'] = 0 if item['tv_count'] is None else item['tv_count'] - item['season_count'] = 0 if item['season_count'] is None else item['season_count'] - - series_1.append(item['movie_count']) - series_2.append(item['tv_count']) - series_3.append(item['season_count']) - #Value switch implemented as the episode count comes with the first item which is for the movies category, - # but needs to be placed at the second item for the TV category - if item['episode_count'] == 0: - series_4.append(_episodes) + for idx, item in enumerate(categories): + if idx == 0: + series_1.append(content['movie_count']) + series_2.append(None) + series_3.append(None) + series_4.append(None) else: - if item['tv_count'] != None: - series_4.append(0) - _episodes = item['episode_count'] + series_1.append(None) + series_2.append(content['tv_count']) + series_3.append(content['season_count']) + series_4.append(content['episode_count']) series_1_output = {'name': 'Movies', 'data': series_1} @@ -610,6 +608,7 @@ class Graphs(object): output = {'categories': categories, 'series': series_output} + return output def get_total_additions_by_resolution(self, time_range='30'): @@ -618,14 +617,18 @@ class Graphs(object): time_range = helpers.cast_to_int(time_range) or 30 timestamp = helpers.timestamp() - time_range * 24 * 60 * 60 - resolution_identifier = '(CASE WHEN media_info LIKE \'%"video_resolution": "4K"%\' THEN "4k" ' \ - 'WHEN media_info LIKE \'%"video_resolution": "1080"%\' THEN "1080" ' \ - 'WHEN media_info LIKE \'%"video_resolution": "720"%\' THEN "720" ' \ - 'WHEN media_info LIKE \'%"video_resolution": "sd"%\' THEN "SD" ELSE "Unknown" END) AS resolution ' + resolution_identifier = '(CASE WHEN media_info LIKE \'%"video_resolution": "4K"%\' THEN "1_4K" ' \ + 'WHEN media_info LIKE \'%"video_resolution": "1080"%\' THEN "2_1080" ' \ + 'WHEN media_info LIKE \'%"video_resolution": "720"%\' THEN "3_720" ' \ + 'WHEN media_info LIKE \'%"video_resolution": "sd"%\' THEN "4_SD" ELSE "5_Unknown" END) AS resolution ' try: - query = 'SELECT ra.resolution, SUM(ra.movie_count) AS movie_count, ' \ - 'SUM(ra.tv_count) AS tv_count, SUM(ra.season_count) AS season_count, SUM(ra.episode_count) AS episode_count ' \ + query = 'SELECT ' \ + 'ra.resolution, ' \ + 'SUM(ra.movie_count) AS movie_count, ' \ + 'SUM(ra.tv_count) AS tv_count, ' \ + 'SUM(ra.season_count) AS season_count, ' \ + 'SUM(ra.episode_count) AS episode_count ' \ 'FROM(' \ 'SELECT ' \ 'raM.resolution, ' \ @@ -678,10 +681,9 @@ class Graphs(object): series_3 = [] series_4 = [] - _episodes = 0 - for idx, item in enumerate(result): - categories.append(item['resolution']) + #remove sorting indicators (like 1_%) + categories.append(item['resolution'][2:]) series_1.append(item['movie_count']) series_2.append(item['tv_count']) From 799a8069ba95eaa12a89d9ad21c56e14214bfa22 Mon Sep 17 00:00:00 2001 From: herby2212 Date: Wed, 18 Aug 2021 20:59:37 +0200 Subject: [PATCH 12/31] library growth graph content --- data/interfaces/default/graphs.html | 104 ++++++++---------- plexpy/graphs.py | 162 ++++++++++++++++++++++------ plexpy/webserve.py | 4 +- 3 files changed, 175 insertions(+), 95 deletions(-) diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index 3594522b..e979d117 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -369,6 +369,7 @@ var _visible = (s.name === series_name) ? !s.visible : s.visible; if(s.name == "Episodes") { document.getElementsByClassName("highcharts-yaxis-title")[0].innerHTML = (_visible) ? "Episodes" : ""; + document.getElementsByClassName("highcharts-yaxis-title")[1].innerHTML = (_visible) ? "Episodes" : ""; } return { name: s.name, visible: _visible @@ -708,12 +709,6 @@ to: i+0.5, color: 'rgba(80,80,80,0.3)' }); - - hc_library_growth_by_day_options.xAxis.plotBands.push({ - from: i-0.5, - to: i+0.5, - color: 'rgba(80,80,80,0.3)' - }); } }); hc_additions_by_day_options.yAxis.min = 0; @@ -721,6 +716,28 @@ hc_additions_by_day_options.series = getGraphVisibility(hc_additions_by_day_options.chart.renderTo, data.series); hc_additions_by_day_options.colors = getGraphColors(data.series); var hc_additions_by_day = new Highcharts.Chart(hc_additions_by_day_options); + } + }); + + $.ajax({ + url: "get_additions_by_date", + type: 'get', + data: { time_range: time_range, growth: 1 }, + dataType: "json", + success: function(data) { + var dateArray = []; + $.each(data.categories, function (i, day) { + dateArray.push(moment(day, 'YYYY-MM-DD').valueOf()); + // Highlight the weekend + if ((moment(day, 'YYYY-MM-DD').format('ddd') === 'Sat') || + (moment(day, 'YYYY-MM-DD').format('ddd') === 'Sun')) { + hc_library_growth_by_day_options.xAxis.plotBands.push({ + from: i-0.5, + to: i+0.5, + color: 'rgba(80,80,80,0.3)' + }); + } + }); hc_library_growth_by_day_options.yAxis.min = 0; hc_library_growth_by_day_options.xAxis.categories = dateArray; @@ -955,22 +972,17 @@ } // Find coordinates within the chart event = chart.pointer.normalize(e); - // Get the hovered point + + // Get the hovered points points = []; chart.series.forEach(function (series, idx) { if(idx == 0) { + // Required to prevent error on initial graph loading series.tooltipOptions.followPointer = false; } points.push(series.searchPoint(event, true)); }); - //point = chart.series[0].searchPoint(event, true); - /* - if (point) { - points[2].highlight(e); - points[1].highlight(e); - points[0].highlight(e); - } - */ + if(!points.includes(undefined)) { chart.tooltip.refresh(points); chart.xAxis[0].drawCrosshair(e, points[0]); @@ -981,50 +993,26 @@ ); }); - //https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/demo/synchronized-charts - Highcharts.Point.prototype.highlight = function (event) { - this.onMouseOver(); // Show the hover marker - var a = [this]; - //this.series.chart.tooltip.refresh(a); // Show the tooltip - this.series.chart.xAxis[0].drawCrosshair(event, this); // Show the crosshair - }; - - //MAYBE NOT EVEN NESSECARY - Highcharts.Pointer.prototype.reset = function (a, b) { - if(this.chart.renderTo.outerHTML.includes("library")) { - return undefined; + function synchronize(point) { + if (chart1.customCrosshair) { // destroy previous crosshair + chart1.customCrosshair.element.remove() } - var c = this.chart - , d = c.hoverSeries - , e = c.hoverPoint - , f = c.hoverPoints - , g = c.tooltip - , h = g && g.shared ? f : e; - (a = a && g && h) && sa(h)[0].plotX === y && (a = !1); - if (a) - g.refresh(h), - e && (e.setState(e.state, !0), - Highcharts.each(c.axes, function(a) { - p(a.options.crosshair && a.options.crosshair.snap, !0) ? a.drawCrosshair(null, e) : a.hideCrosshair() - })); - else { - if (e) - e.onMouseOut(); - f && Highcharts.each(f, function(a) { - a.setState() - }); - if (d) - d.onMouseOut(); - g && g.hide(b); - if (this._onDocumentMouseMove) - Highcharts.removeEvent(document, "mousemove", this._onDocumentMouseMove), - this._onDocumentMouseMove = null; - Highcharts.each(c.axes, function(a) { - a.hideCrosshair() - }); - this.hoverX = c.hoverPoints = c.hoverPoint = null - } - }; + if (chart2.customCrosshair) { + chart2.customCrosshair.element.remove() + } + + // show tooltip on second chart + chart2.tooltip.refresh(chart2.series[0].points[point.x]); + + // render crosshairs + chart1.customCrosshair = chart1.renderer.rect(point.plotX + chart1.plotLeft - 1, chart1.plotTop, 1, chart1.plotSizeY).attr({ + fill: 'red' + }).add() + + chart2.customCrosshair = chart2.renderer.rect(chart2.series[0].points[point.x].plotX + chart2.plotLeft - 1, chart2.plotTop, 1, chart2.plotSizeY).attr({ + fill: 'red' + }).add() + } \ No newline at end of file diff --git a/plexpy/graphs.py b/plexpy/graphs.py index 16569449..357581b4 100644 --- a/plexpy/graphs.py +++ b/plexpy/graphs.py @@ -421,48 +421,120 @@ class Graphs(object): 'series': series_output} return output - def get_total_additions_per_day(self, time_range='30'): + def get_total_additions_per_day(self, time_range='30', growth=False): monitor_db = database.MonitorDatabase() time_range = helpers.cast_to_int(time_range) or 30 timestamp = helpers.timestamp() - time_range * 24 * 60 * 60 try: - query = 'SELECT raM.date_added, ' \ - 'SUM(CASE WHEN raM.media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ - '0 AS tv_count, ' \ - '0 AS season_count, ' \ - 'SUM(CASE WHEN raM.media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ - 'FROM (SELECT *, date(added_at, "unixepoch", "localtime") AS date_added ' \ - ' FROM recently_added ' \ - ' WHERE (media_type = "movie" OR media_type = "episode") AND added_at >= %s) AS raM ' \ - 'GROUP BY raM.date_added ' \ - 'UNION ALL ' \ - 'SELECT raG.date_added, ' \ - '0 AS movie_count, ' \ - 'SUM(CASE WHEN NOT raG.grandparent_rating_key = "" THEN 1 ELSE 0 END) AS tv_count, ' \ - '0 AS season_count, ' \ - '0 AS episode_count ' \ - 'FROM (SELECT *, date(added_at, "unixepoch", "localtime") AS date_added ' \ - ' FROM recently_added ' \ - ' WHERE NOT grandparent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ - ' GROUP BY grandparent_rating_key) AS raG ' \ - 'GROUP BY raG.date_added ' \ - 'UNION ALL ' \ - 'SELECT raS.date_added, ' \ - '0 AS movie_count, ' \ - '0 AS tv_count, ' \ - 'SUM(CASE WHEN NOT raS.parent_rating_key = "" THEN 1 ELSE 0 END) AS season_count, ' \ - '0 AS episode_count ' \ - 'FROM (SELECT *, date(added_at, "unixepoch", "localtime") AS date_added ' \ - ' FROM recently_added ' \ - ' WHERE NOT parent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ - ' GROUP BY parent_rating_key) AS raS ' \ - 'GROUP BY raS.date_added ' \ - 'ORDER BY date_added' % (timestamp, timestamp, timestamp) - - result = monitor_db.select(query) + if growth: + query = 'SELECT ' \ + '0 AS date_added, ' \ + 'SUM(ra.movie_count) AS movie_count, ' \ + 'SUM(ra.tv_count) AS tv_count, ' \ + 'SUM(ra.season_count) AS season_count, ' \ + 'SUM(ra.episode_count) AS episode_count ' \ + 'FROM (' \ + 'SELECT ' \ + 'SUM(CASE WHEN raM.media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ + '0 AS tv_count, ' \ + '0 AS season_count, ' \ + 'SUM(CASE WHEN raM.media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ + 'FROM (SELECT * ' \ + 'FROM recently_added ' \ + 'WHERE (media_type = "movie" OR media_type = "episode") AND added_at < %s) AS raM ' \ + 'UNION ALL ' \ + 'SELECT ' \ + '0 AS movie_count, ' \ + 'SUM(CASE WHEN NOT raG.grandparent_rating_key = "" THEN 1 ELSE 0 END) AS tv_count, ' \ + '0 AS season_count, ' \ + '0 AS episode_count ' \ + 'FROM (SELECT * ' \ + 'FROM recently_added ' \ + 'WHERE NOT grandparent_rating_key = "" AND media_type = "episode" AND added_at < %s ' \ + 'GROUP BY grandparent_rating_key) AS raG ' \ + 'UNION ALL ' \ + 'SELECT ' \ + '0 AS movie_count, ' \ + '0 AS tv_count, ' \ + 'SUM(CASE WHEN NOT raS.parent_rating_key = "" THEN 1 ELSE 0 END) AS season_count, ' \ + '0 AS episode_count ' \ + 'FROM (SELECT * ' \ + 'FROM recently_added ' \ + 'WHERE NOT parent_rating_key = "" AND media_type = "episode" AND added_at < %s ' \ + 'GROUP BY parent_rating_key) AS raS ' \ + ') AS ra ' \ + 'UNION ALL ' \ + 'SELECT raM.date_added, ' \ + 'SUM(CASE WHEN raM.media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ + '0 AS tv_count, ' \ + '0 AS season_count, ' \ + 'SUM(CASE WHEN raM.media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ + 'FROM (SELECT *, date(added_at, "unixepoch", "localtime") AS date_added ' \ + ' FROM recently_added ' \ + ' WHERE (media_type = "movie" OR media_type = "episode") AND added_at >= %s) AS raM ' \ + 'GROUP BY raM.date_added ' \ + 'UNION ALL ' \ + 'SELECT raG.date_added, ' \ + '0 AS movie_count, ' \ + 'SUM(CASE WHEN NOT raG.grandparent_rating_key = "" THEN 1 ELSE 0 END) AS tv_count, ' \ + '0 AS season_count, ' \ + '0 AS episode_count ' \ + 'FROM (SELECT *, date(added_at, "unixepoch", "localtime") AS date_added ' \ + ' FROM recently_added ' \ + ' WHERE NOT grandparent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ + ' GROUP BY grandparent_rating_key) AS raG ' \ + 'GROUP BY raG.date_added ' \ + 'UNION ALL ' \ + 'SELECT raS.date_added, ' \ + '0 AS movie_count, ' \ + '0 AS tv_count, ' \ + 'SUM(CASE WHEN NOT raS.parent_rating_key = "" THEN 1 ELSE 0 END) AS season_count, ' \ + '0 AS episode_count ' \ + 'FROM (SELECT *, date(added_at, "unixepoch", "localtime") AS date_added ' \ + ' FROM recently_added ' \ + ' WHERE NOT parent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ + ' GROUP BY parent_rating_key) AS raS ' \ + 'GROUP BY raS.date_added ' \ + 'ORDER BY date_added' % (timestamp, timestamp, timestamp, + timestamp, timestamp, timestamp) + result = monitor_db.select(query) + else: + query = 'SELECT raM.date_added, ' \ + 'SUM(CASE WHEN raM.media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ + '0 AS tv_count, ' \ + '0 AS season_count, ' \ + 'SUM(CASE WHEN raM.media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ + 'FROM (SELECT *, date(added_at, "unixepoch", "localtime") AS date_added ' \ + ' FROM recently_added ' \ + ' WHERE (media_type = "movie" OR media_type = "episode") AND added_at >= %s) AS raM ' \ + 'GROUP BY raM.date_added ' \ + 'UNION ALL ' \ + 'SELECT raG.date_added, ' \ + '0 AS movie_count, ' \ + 'SUM(CASE WHEN NOT raG.grandparent_rating_key = "" THEN 1 ELSE 0 END) AS tv_count, ' \ + '0 AS season_count, ' \ + '0 AS episode_count ' \ + 'FROM (SELECT *, date(added_at, "unixepoch", "localtime") AS date_added ' \ + ' FROM recently_added ' \ + ' WHERE NOT grandparent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ + ' GROUP BY grandparent_rating_key) AS raG ' \ + 'GROUP BY raG.date_added ' \ + 'UNION ALL ' \ + 'SELECT raS.date_added, ' \ + '0 AS movie_count, ' \ + '0 AS tv_count, ' \ + 'SUM(CASE WHEN NOT raS.parent_rating_key = "" THEN 1 ELSE 0 END) AS season_count, ' \ + '0 AS episode_count ' \ + 'FROM (SELECT *, date(added_at, "unixepoch", "localtime") AS date_added ' \ + ' FROM recently_added ' \ + ' WHERE NOT parent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ + ' GROUP BY parent_rating_key) AS raS ' \ + 'GROUP BY raS.date_added ' \ + 'ORDER BY date_added' % (timestamp, timestamp, timestamp) + result = monitor_db.select(query) except Exception as e: logger.warn("Tautulli Graphs :: Unable to execute database query for get_total_additions_per_day: %s." % e) return None @@ -478,6 +550,12 @@ class Graphs(object): series_3 = [] series_4 = [] + if growth: + base_value_1 = result[0]['movie_count'] + base_value_2 = result[0]['tv_count'] + base_value_3 = result[0]['season_count'] + base_value_4 = result[0]['episode_count'] + for date_item in sorted(date_list): date_string = date_item.strftime('%Y-%m-%d') categories.append(date_string) @@ -498,6 +576,20 @@ class Graphs(object): series_3.append(series_3_value) series_4.append(series_4_value) + if growth: + for idx, day in enumerate(series_1): + series_1[idx] = base_value_1 + day + base_value_1 += day + for idx, day in enumerate(series_2): + series_2[idx] = base_value_2 + day + base_value_2 += day + for idx, day in enumerate(series_3): + series_3[idx] = base_value_3 + day + base_value_3 += day + for idx, day in enumerate(series_4): + series_4[idx] = base_value_4 + day + base_value_4 += day + series_1_output = {'name': 'Movies', 'data': series_1} series_2_output = {'name': 'Shows', diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 8cb580e3..0dfdae17 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -2491,9 +2491,9 @@ class WebInterface(object): @cherrypy.tools.json_out() @requireAuth() #called additions instead of adds so it isn't blocked by adblockers... - def get_additions_by_date(self, time_range='30'): + def get_additions_by_date(self, time_range='30', growth=False): graph = graphs.Graphs() - result = graph.get_total_additions_per_day(time_range=time_range) + result = graph.get_total_additions_per_day(time_range=time_range, growth=growth) if result: return result From 13e01c751aa15175fd5767afd79e55dd687efb4c Mon Sep 17 00:00:00 2001 From: herby2212 Date: Thu, 19 Aug 2021 14:23:56 +0200 Subject: [PATCH 13/31] fix console error, tooltip shadow, cursor --- data/interfaces/default/graphs.html | 10 ++++++++-- .../default/js/graphs/library_growth_by_day.js | 10 +--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index e979d117..d3206787 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -980,10 +980,16 @@ // Required to prevent error on initial graph loading series.tooltipOptions.followPointer = false; } - points.push(series.searchPoint(event, true)); + var _point = series.searchPoint(event, true); + if(_point == undefined) { + return; + } + if(_point.series.visible == true) { + points.push(_point); + } }); - if(!points.includes(undefined)) { + if(points.length && !points.includes(undefined)) { chart.tooltip.refresh(points); chart.xAxis[0].drawCrosshair(e, points[0]); chart.xAxis[0].drawCrosshair(e, points[points.length]); diff --git a/data/interfaces/default/js/graphs/library_growth_by_day.js b/data/interfaces/default/js/graphs/library_growth_by_day.js index 09fd0aa9..f5ddaf1f 100644 --- a/data/interfaces/default/js/graphs/library_growth_by_day.js +++ b/data/interfaces/default/js/graphs/library_growth_by_day.js @@ -27,14 +27,6 @@ var hc_library_growth_by_day_options = { series: { allowPointSelect: false, threshold: 0, - cursor: 'pointer', - point: { - events: { - click: function () { - selectHandler(this.category, this.series.name); - } - } - }, events: { legendItemClick: function() { setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name); @@ -46,7 +38,7 @@ var hc_library_growth_by_day_options = { type: 'datetime', labels: { formatter: function() { - return moment(this.value).format("MMM D"); + return moment(this.value).format("YY MMM D"); }, style: { color: '#aaa' From 76581f3ab522e01f9dbd874aee913915d522804a Mon Sep 17 00:00:00 2001 From: herby2212 Date: Thu, 19 Aug 2021 16:25:57 +0200 Subject: [PATCH 14/31] sync enable/disable axis, labels, storage --- data/interfaces/default/graphs.html | 53 ++++++++++++------- .../default/js/graphs/additions_by_day.js | 1 + .../js/graphs/library_growth_by_day.js | 3 +- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index d3206787..aa2ee2bc 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -362,15 +362,39 @@ }); } + var syncLinks = { + 'library_growth_by_day': 'graph_additions_by_day', + 'graph_additions_by_day': 'library_growth_by_day' + } + + function syncGraphs(instance, chart_name, series_name) { + var otherChart = Highcharts.charts.find(c => c['renderTo'].id == syncLinks[chart_name]); + sisterSeries = otherChart.series.find(s => s['name'] == series_name); + + // Chart Visibility in storage - needs to be done before setting axis visibility + setGraphVisibility(syncLinks[chart_name], otherChart.series, series_name); + + // Axis visibility + if (sisterSeries) { + if (instance.visible) { + sisterSeries.hide(); + } else { + sisterSeries.show(); + } + } + + // Axis name visibility + if (series_name == "Episodes") { + document.getElementsByClassName("highcharts-yaxis-title")[0].innerHTML = (instance.visible) ? "" : "Episodes"; + document.getElementsByClassName("highcharts-yaxis-title")[1].innerHTML = (instance.visible) ? "" : "Episodes"; + } + } + function setGraphVisibility(chart_name, data_series, series_name) { var chart_key = 'HighCharts_' + chart_name; var chart_visibility = data_series.map(function(s) { var _visible = (s.name === series_name) ? !s.visible : s.visible; - if(s.name == "Episodes") { - document.getElementsByClassName("highcharts-yaxis-title")[0].innerHTML = (_visible) ? "Episodes" : ""; - document.getElementsByClassName("highcharts-yaxis-title")[1].innerHTML = (_visible) ? "Episodes" : ""; - } return { name: s.name, visible: _visible } @@ -976,24 +1000,17 @@ // Get the hovered points points = []; chart.series.forEach(function (series, idx) { - if(idx == 0) { - // Required to prevent error on initial graph loading - series.tooltipOptions.followPointer = false; - } var _point = series.searchPoint(event, true); - if(_point == undefined) { - return; - } - if(_point.series.visible == true) { - points.push(_point); + if(_point && _point.series.visible == true) { + points.push(_point); } }); - if(points.length && !points.includes(undefined)) { - chart.tooltip.refresh(points); - chart.xAxis[0].drawCrosshair(e, points[0]); - chart.xAxis[0].drawCrosshair(e, points[points.length]); - } + if(points.length && !points.includes(undefined)) { + chart.tooltip.refresh(points); + chart.xAxis[0].drawCrosshair(e, points[0]); + chart.xAxis[0].drawCrosshair(e, points[points.length]); + } } } ); diff --git a/data/interfaces/default/js/graphs/additions_by_day.js b/data/interfaces/default/js/graphs/additions_by_day.js index a64a6acc..6793f1d5 100644 --- a/data/interfaces/default/js/graphs/additions_by_day.js +++ b/data/interfaces/default/js/graphs/additions_by_day.js @@ -36,6 +36,7 @@ var hc_additions_by_day_options = { }, events: { legendItemClick: function() { + syncGraphs(this, this.chart.renderTo.id, this.name); setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name); } } diff --git a/data/interfaces/default/js/graphs/library_growth_by_day.js b/data/interfaces/default/js/graphs/library_growth_by_day.js index f5ddaf1f..3fee8ef1 100644 --- a/data/interfaces/default/js/graphs/library_growth_by_day.js +++ b/data/interfaces/default/js/graphs/library_growth_by_day.js @@ -28,7 +28,8 @@ var hc_library_growth_by_day_options = { allowPointSelect: false, threshold: 0, events: { - legendItemClick: function() { + legendItemClick: function(event) { + syncGraphs(this, this.chart.renderTo.id, this.name); setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name); } } From 5c8abd30488be4b89ae658220f75595fb41bb628 Mon Sep 17 00:00:00 2001 From: herby2212 Date: Thu, 19 Aug 2021 17:40:17 +0200 Subject: [PATCH 15/31] fix tooltip position->sync graphs based on x value --- data/interfaces/default/graphs.html | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index aa2ee2bc..52b5480d 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -369,7 +369,7 @@ function syncGraphs(instance, chart_name, series_name) { var otherChart = Highcharts.charts.find(c => c['renderTo'].id == syncLinks[chart_name]); - sisterSeries = otherChart.series.find(s => s['name'] == series_name); + var sisterSeries = otherChart.series.find(s => s['name'] == series_name); // Chart Visibility in storage - needs to be done before setting axis visibility setGraphVisibility(syncLinks[chart_name], otherChart.series, series_name); @@ -979,16 +979,19 @@ } }); + // https://jsfiddle.net/BlackLabel/5wq9sdbp ['mousemove', 'touchmove', 'touchstart'].forEach(function (eventType) { document.getElementById('section-library-graphs').addEventListener( eventType, function (e) { var chart, + otherChart, point, points, i, event; + for (i = 0; i < Highcharts.charts.length; i = i + 1) { chart = Highcharts.charts[i]; if(chart.options.chart.type != "line") { @@ -997,6 +1000,8 @@ // Find coordinates within the chart event = chart.pointer.normalize(e); + otherChart = Highcharts.charts.find(c => c['renderTo'].id == syncLinks[chart.renderTo.id]); + // Get the hovered points points = []; chart.series.forEach(function (series, idx) { @@ -1009,8 +1014,25 @@ if(points.length && !points.includes(undefined)) { chart.tooltip.refresh(points); chart.xAxis[0].drawCrosshair(e, points[0]); - chart.xAxis[0].drawCrosshair(e, points[points.length]); + //chart.xAxis[0].drawCrosshair(e, points[points.length]); + + pointsChart2 = [] + number = 0; + otherChart.series.forEach(function(_series, idx) { + if(_series.visible) { + var _point = _series.points[points[number].x]; + pointsChart2.push(_point); + number++; + } + }); + otherChart.tooltip.refresh(pointsChart2); + otherChart.xAxis[0].drawCrosshair(e, pointsChart2[0]); } + + /** + * If a chart matching chart is found the other will be retrieved through the syncLink so no additional iteration is required. + **/ + break; } } ); From 13bf9f9b4b42385b4c94124a4deabf434440000f Mon Sep 17 00:00:00 2001 From: herby2212 Date: Thu, 19 Aug 2021 19:54:09 +0200 Subject: [PATCH 16/31] cleanup; fix sync data range change or tab switch --- data/interfaces/default/graphs.html | 151 +++++++++++++--------------- 1 file changed, 69 insertions(+), 82 deletions(-) diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index 52b5480d..31be2c97 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -295,6 +295,7 @@ \ No newline at end of file From 94f2dbeaffc94f778a12feadb652b56bd6f5e822 Mon Sep 17 00:00:00 2001 From: herby2212 Date: Fri, 20 Aug 2021 14:02:20 +0200 Subject: [PATCH 17/31] library_stats_items table creation and data setup --- data/interfaces/default/graphs.html | 2 +- plexpy/__init__.py | 18 ++++++++++++ plexpy/activity_handler.py | 3 +- plexpy/datafactory.py | 45 +++++++++++++++++++++++++++++ plexpy/libraries.py | 38 +++++++++++++++++++++++- 5 files changed, 103 insertions(+), 3 deletions(-) diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index 31be2c97..7c9c9055 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -450,7 +450,7 @@ } /** - * If a chart matching chart is found the other will be retrieved through the syncLink so no additional iteration is required. + * If a matching chart is found the other will be retrieved through the syncLink so no additional iteration is required. **/ break; } diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 8b376938..408980c2 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -787,6 +787,14 @@ def dbcheck(): 'media_info TEXT)' ) + # library_stats_items :: This table keeps record of all added items + c_db.execute( + 'CREATE TABLE IF NOT EXISTS library_stats_items (id INTEGER PRIMARY KEY AUTOINCREMENT, ' + 'added_at INTEGER, updated_at INTEGER, last_viewed_at INTEGER, pms_identifier TEXT, section_id INTEGER, ' + 'library_name TEXT, rating_key INTEGER, parent_rating_key INTEGER, grandparent_rating_key INTEGER, ' + 'media_type TEXT, media_info TEXT, user_ratings TEXT)' + ) + # mobile_devices table :: This table keeps record of devices linked with the mobile app c_db.execute( 'CREATE TABLE IF NOT EXISTS mobile_devices (id INTEGER PRIMARY KEY AUTOINCREMENT, ' @@ -2493,6 +2501,16 @@ def dbcheck(): 'ON "session_history_media_info" ("transcode_decision")' ) + # Create library_stats_items table indices + c_db.execute( + 'CREATE INDEX IF NOT EXISTS "idx_library_stats_items_media_type" ' + 'ON "library_stats_items" ("media_type")' + ) + c_db.execute( + 'CREATE INDEX IF NOT EXISTS "idx_library_stats_items_rating_key" ' + 'ON "library_stats_items" ("rating_key")' + ) + # Create lookup table indices c_db.execute( 'CREATE UNIQUE INDEX IF NOT EXISTS "idx_tvmaze_lookup" ' diff --git a/plexpy/activity_handler.py b/plexpy/activity_handler.py index a45a2cd4..4de76f7e 100644 --- a/plexpy/activity_handler.py +++ b/plexpy/activity_handler.py @@ -681,7 +681,7 @@ def on_created(rating_key, **kwargs): data_factory = datafactory.DataFactory() if 'child_keys' not in kwargs: - if data_factory.get_recently_added_item(rating_key): + if data_factory.get_recently_added_item(rating_key) and data_factory.get_library_stats_item(rating_key): logger.debug("Tautulli TimelineHandler :: Library item %s added already. Not notifying again." % str(rating_key)) notify = False @@ -697,6 +697,7 @@ def on_created(rating_key, **kwargs): for key in all_keys: data_factory.set_recently_added_item(key) + data_factory.set_library_stats_item(key) logger.debug("Added %s items to the recently_added database table." % str(len(all_keys))) diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index b724153b..5f04cf0d 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -1999,3 +1999,48 @@ class DataFactory(object): return False return True + + + def get_library_stats_item(self, rating_key=''): + monitor_db = database.MonitorDatabase() + + if rating_key: + try: + query = 'SELECT * FROM library_stats_items WHERE rating_key = ?' + result = monitor_db.select(query=query, args=[rating_key]) + except Exception as e: + logger.warn("Tautulli DataFactory :: Unable to execute database query for get_library_stats_item: %s." % e) + return [] + else: + return [] + + return result + + def set_library_stats_item(self, rating_key=''): + monitor_db = database.MonitorDatabase() + + pms_connect = pmsconnect.PmsConnect() + metadata = pms_connect.get_metadata_details(rating_key=rating_key, skip_cache=True, media_info=True) + + keys = {'rating_key': metadata['rating_key']} + + values = {'added_at': metadata['added_at'], + 'updated_at': metadata['updated_at'], + 'last_viewed_at': metadata['last_viewed_at'], + 'section_id': metadata['section_id'], + 'library_name': metadata['library_name'], + 'parent_rating_key': metadata['parent_rating_key'], + 'grandparent_rating_key': metadata['grandparent_rating_key'], + 'media_type': metadata['media_type'], + 'media_info': json.dumps(metadata['media_info']), + # TODO json array with ratings from all users + 'user_ratings': '' + } + + try: + monitor_db.upsert(table_name='library_stats_items', key_dict=keys, value_dict=values) + except Exception as e: + logger.warn("Tautulli DataFactory :: Unable to execute database query for set_library_stats_item: %s." % e) + return False + + return True \ No newline at end of file diff --git a/plexpy/libraries.py b/plexpy/libraries.py index 6ec462bc..c8fb5ad8 100644 --- a/plexpy/libraries.py +++ b/plexpy/libraries.py @@ -35,6 +35,7 @@ if plexpy.PYTHON2: import pmsconnect import session import users + import datafactory from plex import Plex else: from plexpy import common @@ -47,6 +48,7 @@ else: from plexpy import session from plexpy import users from plexpy.plex import Plex + from plexpy import datafactory def refresh_libraries(): @@ -64,19 +66,25 @@ def refresh_libraries(): library_keys = [] new_keys = [] + ratingKeys = [] # Keep track of section_id to update is_active status section_ids = [common.LIVE_TV_SECTION_ID] # Live TV library always considered active + _pms = pmsconnect.PmsConnect() + _datafactory = datafactory.DataFactory() + for section in library_sections: section_ids.append(helpers.cast_to_int(section['section_id'])) + section_type = section['section_type'] + section_keys = {'server_id': server_id, 'section_id': section['section_id']} section_values = {'server_id': server_id, 'section_id': section['section_id'], 'section_name': section['section_name'], - 'section_type': section['section_type'], + 'section_type': section_type, 'agent': section['agent'], 'thumb': section['thumb'], 'art': section['art'], @@ -93,6 +101,34 @@ def refresh_libraries(): if result == 'insert': new_keys.append(section['section_id']) + # Push Data to library_sections table + # Placed here as statistics should represent current library status (be in sync) + # if library update disabled => statistics update also disabled => will me moved to + # seperate refresh function as it has long runtimes + # initial run: 16min for 16000 item (movies + shows + seasons + episodes + track + album + artist) + # update run: 8min -,- + _resultSet = [] + _resultSet.append(_pms.get_library_children_details(section_id=section['section_id'], section_type=section['section_type'], get_media_info=False)) + + # Add additional library contents for easier filtering at graph queries + if section_type == 'show': + _resultSet.append(_pms.get_library_children_details(section_id=section['section_id'], section_type='season', get_media_info=False)) + _resultSet.append(_pms.get_library_children_details(section_id=section['section_id'], section_type='episode', get_media_info=False)) + + if section_type == 'artist': + _resultSet.append(_pms.get_library_children_details(section_id=section['section_id'], section_type='album', get_media_info=False)) + _resultSet.append(_pms.get_library_children_details(section_id=section['section_id'], section_type='track', get_media_info=False)) + + for result in _resultSet: + for item in result['children_list']: + if item['rating_key'] not in ratingKeys: + ratingKeys.append(item['rating_key']) + + ratingKeys = sorted(ratingKeys, key=lambda k: helpers.cast_to_int(k), reverse=False) + # TEMP disabled due to long runtime on startup -> to prevent it + for key in ratingKeys: + _datafactory.set_library_stats_item(rating_key=key) + add_live_tv_library(refresh=True) query = 'UPDATE library_sections SET is_active = 0 WHERE server_id != ? OR ' \ From e4122a04c1dc125f8289fd067d87f818aad1d13d Mon Sep 17 00:00:00 2001 From: herby2212 Date: Fri, 20 Aug 2021 15:58:54 +0200 Subject: [PATCH 18/31] library stats data refresh settings implementation --- data/interfaces/default/settings.html | 19 ++++++++ plexpy/__init__.py | 10 +++++ plexpy/activity_handler.py | 3 +- plexpy/config.py | 8 ++++ plexpy/libraries.py | 64 ++++++++++++++++++--------- plexpy/webserve.py | 4 +- 6 files changed, 85 insertions(+), 23 deletions(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 6b6339c8..ac3024c3 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -940,6 +940,25 @@

    Refresh the libraries list when Tautulli starts.

    +
    + +
    +
    + +
    + +
    +

    The interval (in hours) Tautulli will request an update of all media items from your Plex Media Server.

    +

    Minimum 6, maximum 24, default 12.

    +

    This process, depending on your library sizes, can take multiple minutes up to half an hour.

    +
    +
    + +

    Refresh the library statistics data when Tautulli starts.

    +
    +

    Plex.tv Authentication

    diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 408980c2..5d23f3ca 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -467,11 +467,15 @@ def initialize_scheduler(): # Refresh the users list and libraries list user_hours = CONFIG.REFRESH_USERS_INTERVAL if 1 <= CONFIG.REFRESH_USERS_INTERVAL <= 24 else 12 library_hours = CONFIG.REFRESH_LIBRARIES_INTERVAL if 1 <= CONFIG.REFRESH_LIBRARIES_INTERVAL <= 24 else 12 + library_stats_data_hours = CONFIG.REFRESH_LIBRARY_STATS_DATA_INTERVAL if 6 <= CONFIG.REFRESH_LIBRARY_STATS_DATA_INTERVAL <= 24 else 12 schedule_job(users.refresh_users, 'Refresh users list', hours=user_hours, minutes=0, seconds=0) schedule_job(libraries.refresh_libraries, 'Refresh libraries list', hours=library_hours, minutes=0, seconds=0) + + schedule_job(libraries.refresh_library_statistics, 'Refresh libraries statistics data', + hours=library_stats_data_hours, minutes=0, seconds=0) schedule_job(activity_pinger.connect_server, 'Check for server response', hours=0, minutes=0, seconds=0) @@ -491,6 +495,9 @@ def initialize_scheduler(): schedule_job(libraries.refresh_libraries, 'Refresh libraries list', hours=0, minutes=0, seconds=0) + schedule_job(libraries.refresh_library_statistics, 'Refresh libraries statistics data', + hours=0, minutes=0, seconds=0) + # Schedule job to reconnect server schedule_job(activity_pinger.connect_server, 'Check for server response', hours=0, minutes=0, seconds=30, args=(False,)) @@ -591,6 +598,9 @@ def startup_refresh(): if CONFIG.PMS_IP and CONFIG.PMS_TOKEN and CONFIG.REFRESH_LIBRARIES_ON_STARTUP: libraries.refresh_libraries() + # Refresh the library stats data on startup + if CONFIG.PMS_IP and CONFIG.PMS_TOKEN and CONFIG.REFRESH_LIBRARY_STATS_DATA_ON_STARTUP: + libraries.refresh_library_statistics() def sig_handler(signum=None, frame=None): if signum is not None: diff --git a/plexpy/activity_handler.py b/plexpy/activity_handler.py index 4de76f7e..a45a2cd4 100644 --- a/plexpy/activity_handler.py +++ b/plexpy/activity_handler.py @@ -681,7 +681,7 @@ def on_created(rating_key, **kwargs): data_factory = datafactory.DataFactory() if 'child_keys' not in kwargs: - if data_factory.get_recently_added_item(rating_key) and data_factory.get_library_stats_item(rating_key): + if data_factory.get_recently_added_item(rating_key): logger.debug("Tautulli TimelineHandler :: Library item %s added already. Not notifying again." % str(rating_key)) notify = False @@ -697,7 +697,6 @@ def on_created(rating_key, **kwargs): for key in all_keys: data_factory.set_recently_added_item(key) - data_factory.set_library_stats_item(key) logger.debug("Added %s items to the recently_added database table." % str(len(all_keys))) diff --git a/plexpy/config.py b/plexpy/config.py index b3fb747b..dc31ce8a 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -180,6 +180,8 @@ _CONFIG_DEFINITIONS = { 'PLEXPY_AUTO_UPDATE': (int, 'General', 0), 'REFRESH_LIBRARIES_INTERVAL': (int, 'Monitoring', 12), 'REFRESH_LIBRARIES_ON_STARTUP': (int, 'Monitoring', 1), + 'REFRESH_LIBRARY_STATS_DATA_INTERVAL': (int, 'Monitoring', 12), + 'REFRESH_LIBRARY_STATS_DATA_ON_STARTUP': (int, 'Monitoring', 1), 'REFRESH_USERS_INTERVAL': (int, 'Monitoring', 12), 'REFRESH_USERS_ON_STARTUP': (int, 'Monitoring', 1), 'SESSION_DB_WRITE_ATTEMPTS': (int, 'Advanced', 5), @@ -580,3 +582,9 @@ class Config(object): self.HTTP_HASHED_PASSWORD = 1 self.CONFIG_VERSION = 20 + + if self.CONFIG_VERSION == 20: + self.REFRESH_LIBRARY_STATS_DATA_INTERVAL = 12 + self.REFRESH_LIBRARY_STATS_DATA_ON_STARTUP = 1 + + self.CONFIG_VERSION = 21 diff --git a/plexpy/libraries.py b/plexpy/libraries.py index c8fb5ad8..29198126 100644 --- a/plexpy/libraries.py +++ b/plexpy/libraries.py @@ -77,14 +77,12 @@ def refresh_libraries(): for section in library_sections: section_ids.append(helpers.cast_to_int(section['section_id'])) - section_type = section['section_type'] - section_keys = {'server_id': server_id, 'section_id': section['section_id']} section_values = {'server_id': server_id, 'section_id': section['section_id'], 'section_name': section['section_name'], - 'section_type': section_type, + 'section_type': section['section_type'], 'agent': section['agent'], 'thumb': section['thumb'], 'art': section['art'], @@ -101,6 +99,47 @@ def refresh_libraries(): if result == 'insert': new_keys.append(section['section_id']) + add_live_tv_library(refresh=True) + + query = 'UPDATE library_sections SET is_active = 0 WHERE server_id != ? OR ' \ + 'section_id NOT IN ({})'.format(', '.join(['?'] * len(section_ids))) + monitor_db.action(query=query, args=[plexpy.CONFIG.PMS_IDENTIFIER] + section_ids) + + if plexpy.CONFIG.HOME_LIBRARY_CARDS == ['first_run_wizard']: + plexpy.CONFIG.__setattr__('HOME_LIBRARY_CARDS', library_keys) + plexpy.CONFIG.write() + else: + new_keys = plexpy.CONFIG.HOME_LIBRARY_CARDS + new_keys + plexpy.CONFIG.__setattr__('HOME_LIBRARY_CARDS', new_keys) + plexpy.CONFIG.write() + + logger.info("Tautulli Libraries :: Libraries list refreshed.") + return True + else: + logger.warn("Tautulli Libraries :: Unable to refresh libraries list.") + return False + +def refresh_library_statistics(): + logger.info("Tautulli Library Statistics :: Requesting library statistics data refresh...") + + server_id = plexpy.CONFIG.PMS_IDENTIFIER + if not server_id: + logger.error("Tautulli Library Statistics :: No PMS identifier, cannot refresh data. Verify server in settings.") + return + + library_sections = pmsconnect.PmsConnect().get_library_details() + + if library_sections: + ratingKeys = [] + + _pms = pmsconnect.PmsConnect() + _datafactory = datafactory.DataFactory() + + # TODO Check if library is deleted in Tautulli? + + for section in library_sections: + section_type = section['section_type'] + # Push Data to library_sections table # Placed here as statistics should represent current library status (be in sync) # if library update disabled => statistics update also disabled => will me moved to @@ -129,27 +168,12 @@ def refresh_libraries(): for key in ratingKeys: _datafactory.set_library_stats_item(rating_key=key) - add_live_tv_library(refresh=True) - - query = 'UPDATE library_sections SET is_active = 0 WHERE server_id != ? OR ' \ - 'section_id NOT IN ({})'.format(', '.join(['?'] * len(section_ids))) - monitor_db.action(query=query, args=[plexpy.CONFIG.PMS_IDENTIFIER] + section_ids) - - if plexpy.CONFIG.HOME_LIBRARY_CARDS == ['first_run_wizard']: - plexpy.CONFIG.__setattr__('HOME_LIBRARY_CARDS', library_keys) - plexpy.CONFIG.write() - else: - new_keys = plexpy.CONFIG.HOME_LIBRARY_CARDS + new_keys - plexpy.CONFIG.__setattr__('HOME_LIBRARY_CARDS', new_keys) - plexpy.CONFIG.write() - - logger.info("Tautulli Libraries :: Libraries list refreshed.") + logger.info("Tautulli Library Statistics :: Data refreshed.") return True else: - logger.warn("Tautulli Libraries :: Unable to refresh libraries list.") + logger.warn("Tautulli Library Statistics :: Unable to refresh data.") return False - def add_live_tv_library(refresh=False): monitor_db = database.MonitorDatabase() result = monitor_db.select_single('SELECT * FROM library_sections ' diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 0dfdae17..376927a1 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -3231,6 +3231,8 @@ class WebInterface(object): "monitor_pms_updates": checked(plexpy.CONFIG.MONITOR_PMS_UPDATES), "refresh_libraries_interval": plexpy.CONFIG.REFRESH_LIBRARIES_INTERVAL, "refresh_libraries_on_startup": checked(plexpy.CONFIG.REFRESH_LIBRARIES_ON_STARTUP), + "refresh_library_stats_data_interval": plexpy.CONFIG.REFRESH_LIBRARY_STATS_DATA_INTERVAL, + "refresh_library_stats_data_on_startup": checked(plexpy.CONFIG.REFRESH_LIBRARY_STATS_DATA_ON_STARTUP), "refresh_users_interval": plexpy.CONFIG.REFRESH_USERS_INTERVAL, "refresh_users_on_startup": checked(plexpy.CONFIG.REFRESH_USERS_ON_STARTUP), "logging_ignore_interval": plexpy.CONFIG.LOGGING_IGNORE_INTERVAL, @@ -3308,7 +3310,7 @@ class WebInterface(object): "api_enabled", "freeze_db", "check_github", "group_history_tables", "pms_url_manual", "week_start_monday", - "refresh_libraries_on_startup", "refresh_users_on_startup", + "refresh_libraries_on_startup", "refresh_users_on_startup", "refresh_library_stats_data_on_startup", "notify_consecutive", "notify_recently_added_upgrade", "notify_group_recently_added_grandparent", "notify_group_recently_added_parent", "notify_new_device_initial_only", From dafb09c8f558df0d508593a5461416a8f1368b7f Mon Sep 17 00:00:00 2001 From: herby2212 Date: Fri, 20 Aug 2021 16:37:41 +0200 Subject: [PATCH 19/31] optimized & based queries on library_stats_items --- plexpy/__init__.py | 4 ++ plexpy/graphs.py | 99 +++++++++++----------------------------------- 2 files changed, 26 insertions(+), 77 deletions(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 5d23f3ca..e21d81af 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -2516,6 +2516,10 @@ def dbcheck(): 'CREATE INDEX IF NOT EXISTS "idx_library_stats_items_media_type" ' 'ON "library_stats_items" ("media_type")' ) + c_db.execute( + 'CREATE INDEX IF NOT EXISTS "idx_library_stats_items_added_at" ' + 'ON "library_stats_items" ("added_at")' + ) c_db.execute( 'CREATE INDEX IF NOT EXISTS "idx_library_stats_items_rating_key" ' 'ON "library_stats_items" ("rating_key")' diff --git a/plexpy/graphs.py b/plexpy/graphs.py index 357581b4..19f92e65 100644 --- a/plexpy/graphs.py +++ b/plexpy/graphs.py @@ -431,40 +431,12 @@ class Graphs(object): if growth: query = 'SELECT ' \ '0 AS date_added, ' \ - 'SUM(ra.movie_count) AS movie_count, ' \ - 'SUM(ra.tv_count) AS tv_count, ' \ - 'SUM(ra.season_count) AS season_count, ' \ - 'SUM(ra.episode_count) AS episode_count ' \ - 'FROM (' \ - 'SELECT ' \ - 'SUM(CASE WHEN raM.media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ - '0 AS tv_count, ' \ - '0 AS season_count, ' \ - 'SUM(CASE WHEN raM.media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ - 'FROM (SELECT * ' \ - 'FROM recently_added ' \ - 'WHERE (media_type = "movie" OR media_type = "episode") AND added_at < %s) AS raM ' \ - 'UNION ALL ' \ - 'SELECT ' \ - '0 AS movie_count, ' \ - 'SUM(CASE WHEN NOT raG.grandparent_rating_key = "" THEN 1 ELSE 0 END) AS tv_count, ' \ - '0 AS season_count, ' \ - '0 AS episode_count ' \ - 'FROM (SELECT * ' \ - 'FROM recently_added ' \ - 'WHERE NOT grandparent_rating_key = "" AND media_type = "episode" AND added_at < %s ' \ - 'GROUP BY grandparent_rating_key) AS raG ' \ - 'UNION ALL ' \ - 'SELECT ' \ - '0 AS movie_count, ' \ - '0 AS tv_count, ' \ - 'SUM(CASE WHEN NOT raS.parent_rating_key = "" THEN 1 ELSE 0 END) AS season_count, ' \ - '0 AS episode_count ' \ - 'FROM (SELECT * ' \ - 'FROM recently_added ' \ - 'WHERE NOT parent_rating_key = "" AND media_type = "episode" AND added_at < %s ' \ - 'GROUP BY parent_rating_key) AS raS ' \ - ') AS ra ' \ + 'SUM(CASE WHEN media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ + 'SUM(CASE WHEN media_type = "show" THEN 1 ELSE 0 END) AS tv_count, ' \ + 'SUM(CASE WHEN media_type = "season" THEN 1 ELSE 0 END) AS season_count, ' \ + 'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ + 'FROM library_stats_items ' \ + 'WHERE added_at < %s ' \ 'UNION ALL ' \ 'SELECT raM.date_added, ' \ 'SUM(CASE WHEN raM.media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ @@ -472,7 +444,7 @@ class Graphs(object): '0 AS season_count, ' \ 'SUM(CASE WHEN raM.media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ 'FROM (SELECT *, date(added_at, "unixepoch", "localtime") AS date_added ' \ - ' FROM recently_added ' \ + ' FROM library_stats_items ' \ ' WHERE (media_type = "movie" OR media_type = "episode") AND added_at >= %s) AS raM ' \ 'GROUP BY raM.date_added ' \ 'UNION ALL ' \ @@ -482,7 +454,7 @@ class Graphs(object): '0 AS season_count, ' \ '0 AS episode_count ' \ 'FROM (SELECT *, date(added_at, "unixepoch", "localtime") AS date_added ' \ - ' FROM recently_added ' \ + ' FROM library_stats_items ' \ ' WHERE NOT grandparent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ ' GROUP BY grandparent_rating_key) AS raG ' \ 'GROUP BY raG.date_added ' \ @@ -493,12 +465,11 @@ class Graphs(object): 'SUM(CASE WHEN NOT raS.parent_rating_key = "" THEN 1 ELSE 0 END) AS season_count, ' \ '0 AS episode_count ' \ 'FROM (SELECT *, date(added_at, "unixepoch", "localtime") AS date_added ' \ - ' FROM recently_added ' \ + ' FROM library_stats_items ' \ ' WHERE NOT parent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ ' GROUP BY parent_rating_key) AS raS ' \ 'GROUP BY raS.date_added ' \ - 'ORDER BY date_added' % (timestamp, timestamp, timestamp, - timestamp, timestamp, timestamp) + 'ORDER BY date_added' % (timestamp, timestamp, timestamp, timestamp) result = monitor_db.select(query) else: @@ -620,40 +591,12 @@ class Graphs(object): try: query = 'SELECT ' \ - 'SUM(ra.movie_count) AS movie_count, ' \ - 'SUM(ra.tv_count) AS tv_count, ' \ - 'SUM(ra.season_count) AS season_count, ' \ - 'SUM(ra.episode_count) AS episode_count ' \ - 'FROM (' \ - 'SELECT ' \ - 'SUM(CASE WHEN raM.media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ - '0 AS tv_count, ' \ - '0 AS season_count, ' \ - 'SUM(CASE WHEN raM.media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ - 'FROM (SELECT * ' \ - 'FROM recently_added ' \ - 'WHERE (media_type = "movie" OR media_type = "episode") AND added_at >= %s) AS raM ' \ - 'UNION ALL ' \ - 'SELECT ' \ - '0 AS movie_count, ' \ - 'SUM(CASE WHEN NOT raG.grandparent_rating_key = "" THEN 1 ELSE 0 END) AS tv_count, ' \ - '0 AS season_count, ' \ - '0 AS episode_count ' \ - 'FROM (SELECT * ' \ - 'FROM recently_added ' \ - 'WHERE NOT grandparent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ - 'GROUP BY grandparent_rating_key) AS raG ' \ - 'UNION ALL ' \ - 'SELECT ' \ - '0 AS movie_count, ' \ - '0 AS tv_count, ' \ - 'SUM(CASE WHEN NOT raS.parent_rating_key = "" THEN 1 ELSE 0 END) AS season_count, ' \ - '0 AS episode_count ' \ - 'FROM (SELECT * ' \ - 'FROM recently_added ' \ - 'WHERE NOT parent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ - 'GROUP BY parent_rating_key) AS raS ' \ - ') AS ra' % (timestamp, timestamp, timestamp) + 'SUM(CASE WHEN media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ + 'SUM(CASE WHEN media_type = "show" THEN 1 ELSE 0 END) AS tv_count, ' \ + 'SUM(CASE WHEN media_type = "season" THEN 1 ELSE 0 END) AS season_count, ' \ + 'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ + 'FROM library_stats_items ' \ + 'WHERE added_at >= %s' % timestamp result = monitor_db.select(query) @@ -712,7 +655,9 @@ class Graphs(object): resolution_identifier = '(CASE WHEN media_info LIKE \'%"video_resolution": "4K"%\' THEN "1_4K" ' \ 'WHEN media_info LIKE \'%"video_resolution": "1080"%\' THEN "2_1080" ' \ 'WHEN media_info LIKE \'%"video_resolution": "720"%\' THEN "3_720" ' \ - 'WHEN media_info LIKE \'%"video_resolution": "sd"%\' THEN "4_SD" ELSE "5_Unknown" END) AS resolution ' + 'WHEN media_info LIKE \'%"video_resolution": "576"%\' THEN "4_576" ' \ + 'WHEN media_info LIKE \'%"video_resolution": "480"%\' THEN "5_480" ' \ + 'WHEN media_info LIKE \'%"video_resolution": "sd"%\' THEN "6_SD" ELSE "7_Unknown" END) AS resolution ' try: query = 'SELECT ' \ @@ -729,7 +674,7 @@ class Graphs(object): '0 AS season_count, ' \ 'SUM(CASE WHEN raM.media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ 'FROM (SELECT *, %s ' \ - 'FROM recently_added ' \ + 'FROM library_stats_items ' \ 'WHERE (media_type = "movie" OR media_type = "episode") AND added_at >= %s) AS raM ' \ 'GROUP BY raM.resolution ' \ 'UNION ALL ' \ @@ -740,7 +685,7 @@ class Graphs(object): '0 AS season_count, ' \ '0 AS episode_count ' \ 'FROM (SELECT *, %s ' \ - 'FROM recently_added ' \ + 'FROM library_stats_items ' \ ' WHERE NOT grandparent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ ' GROUP BY grandparent_rating_key) AS raG ' \ 'GROUP BY raG.resolution ' \ @@ -752,7 +697,7 @@ class Graphs(object): 'SUM(CASE WHEN NOT raS.parent_rating_key = "" THEN 1 ELSE 0 END) AS season_count, ' \ '0 AS episode_count ' \ 'FROM (SELECT *, %s ' \ - 'FROM recently_added ' \ + 'FROM library_stats_items ' \ 'WHERE NOT parent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ 'GROUP BY parent_rating_key) AS raS ' \ 'GROUP BY raS.resolution) AS ra ' \ From 827fff30af84ee389130f2778af2429c804eb7b9 Mon Sep 17 00:00:00 2001 From: herby2212 Date: Sat, 21 Aug 2021 15:03:36 +0200 Subject: [PATCH 20/31] cleanup & switch to updated_at --- data/interfaces/default/graphs.html | 123 +++++++++--------- .../default/js/graphs/additions_by_day.js | 4 +- .../js/graphs/library_growth_by_day.js | 2 +- plexpy/__init__.py | 2 +- plexpy/graphs.py | 40 +++--- plexpy/libraries.py | 4 - 6 files changed, 88 insertions(+), 87 deletions(-) diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index 7c9c9055..8bf9b57d 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -368,7 +368,7 @@ 'graph_additions_by_day': 'library_growth_by_day' } - function syncGraphs(instance, chart_name, series_name) { + function syncGraphs(instance, chart_name, series_name, browserEvent) { var otherChart = _Charts.find(c => c['renderTo'].id == syncLinks[chart_name]); var sisterSeries = otherChart.series.find(s => s['name'] == series_name); @@ -389,6 +389,9 @@ document.getElementsByClassName("highcharts-yaxis-title")[0].innerHTML = (instance.visible) ? "" : "Episodes"; document.getElementsByClassName("highcharts-yaxis-title")[1].innerHTML = (instance.visible) ? "" : "Episodes"; } + + // Tooltip + syncTooltip(browserEvent); } @@ -397,66 +400,67 @@ ['mousemove', 'touchmove', 'touchstart'].forEach(function (eventType) { document.getElementById('section-library-graphs').addEventListener( eventType, - function (e) { - var chart, - otherChart, - point, - points, - i, - event; - - - for (i = 0; i < Highcharts.charts.length; i = i + 1) { - if(Highcharts.charts.includes(undefined)) { - _Charts = Highcharts.charts.filter(chart => chart !== undefined); - } else { - _Charts = Highcharts.charts; - } - - chart = _Charts[i]; - if(chart.options.chart.type != "line" || !syncLinks.hasOwnProperty(chart.renderTo.id)) { - continue; - } - // Find coordinates within the chart - event = chart.pointer.normalize(e); - - otherChart = _Charts.find(c => c['renderTo'].id == syncLinks[chart.renderTo.id]); - - // Get the hovered points - points = []; - chart.series.forEach(function (series, idx) { - var _point = series.searchPoint(event, true); - if(_point && _point.series.visible == true) { - points.push(_point); - } - }); - - if(points.length && !points.includes(undefined)) { - chart.tooltip.refresh(points); - chart.xAxis[0].drawCrosshair(e, points[0]); - //chart.xAxis[0].drawCrosshair(e, points[points.length]); - - pointsChart2 = [] - number = 0; - otherChart.series.forEach(function(_series, idx) { - if(_series.visible) { - var _point = _series.points[points[number].x]; - pointsChart2.push(_point); - number++; - } - }); - otherChart.tooltip.refresh(pointsChart2); - otherChart.xAxis[0].drawCrosshair(e, pointsChart2[0]); - } - - /** - * If a matching chart is found the other will be retrieved through the syncLink so no additional iteration is required. - **/ - break; - } + function(e) { + syncTooltip(e); } ); }); + + function syncTooltip(e) { + var chart, otherChart, + point, points, + i, event; + + for (i = 0; i < Highcharts.charts.length; i = i + 1) { + if(Highcharts.charts.includes(undefined)) { + _Charts = Highcharts.charts.filter(chart => chart !== undefined); + } else { + _Charts = Highcharts.charts; + } + + chart = _Charts[i]; + if(chart.options.chart.type != "line" || !syncLinks.hasOwnProperty(chart.renderTo.id)) { + continue; + } + + // Find coordinates within the chart + event = chart.pointer.normalize(e); + + otherChart = _Charts.find(c => c['renderTo'].id == syncLinks[chart.renderTo.id]); + + // Get the hovered points + points = []; + chart.series.forEach(function (series, idx) { + var _point = series.searchPoint(event, true); + if(_point && _point.series.visible == true) { + points.push(_point); + } + }); + + if(points.length && !points.includes(undefined)) { + chart.tooltip.refresh(points); + chart.xAxis[0].drawCrosshair(e, points[0]); + //chart.xAxis[0].drawCrosshair(e, points[points.length]); + + pointsChart2 = [] + number = 0; + otherChart.series.forEach(function(_series, idx) { + if(_series.visible) { + var _point = _series.points[points[number].x]; + pointsChart2.push(_point); + number++; + } + }); + otherChart.tooltip.refresh(pointsChart2); + otherChart.xAxis[0].drawCrosshair(e, pointsChart2[0]); + } + + /** + * If a matching chart is found the other will be retrieved through the syncLink so no additional iteration is required. + **/ + break; + } + } function setGraphVisibility(chart_name, data_series, series_name) { var chart_key = 'HighCharts_' + chart_name; @@ -782,7 +786,8 @@ $('#days-selection').show(); $('#months-selection').hide(); - setGraphFormat(yaxis); + // Fixed as graph uses own measurement 'addition count' with formatting of 'plays' + setGraphFormat("plays"); $.ajax({ url: "get_additions_by_date", diff --git a/data/interfaces/default/js/graphs/additions_by_day.js b/data/interfaces/default/js/graphs/additions_by_day.js index 6793f1d5..bc433bd6 100644 --- a/data/interfaces/default/js/graphs/additions_by_day.js +++ b/data/interfaces/default/js/graphs/additions_by_day.js @@ -35,8 +35,8 @@ var hc_additions_by_day_options = { } }, events: { - legendItemClick: function() { - syncGraphs(this, this.chart.renderTo.id, this.name); + legendItemClick: function(event) { + syncGraphs(this, this.chart.renderTo.id, this.name, event.browserEvent); setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name); } } diff --git a/data/interfaces/default/js/graphs/library_growth_by_day.js b/data/interfaces/default/js/graphs/library_growth_by_day.js index 3fee8ef1..db93e55e 100644 --- a/data/interfaces/default/js/graphs/library_growth_by_day.js +++ b/data/interfaces/default/js/graphs/library_growth_by_day.js @@ -29,7 +29,7 @@ var hc_library_growth_by_day_options = { threshold: 0, events: { legendItemClick: function(event) { - syncGraphs(this, this.chart.renderTo.id, this.name); + syncGraphs(this, this.chart.renderTo.id, this.name, event.browserEvent); setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name); } } diff --git a/plexpy/__init__.py b/plexpy/__init__.py index e21d81af..9027215d 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -797,7 +797,7 @@ def dbcheck(): 'media_info TEXT)' ) - # library_stats_items :: This table keeps record of all added items + # library_stats_items table :: This table keeps record of all added items c_db.execute( 'CREATE TABLE IF NOT EXISTS library_stats_items (id INTEGER PRIMARY KEY AUTOINCREMENT, ' 'added_at INTEGER, updated_at INTEGER, last_viewed_at INTEGER, pms_identifier TEXT, section_id INTEGER, ' diff --git a/plexpy/graphs.py b/plexpy/graphs.py index 19f92e65..d4219d51 100644 --- a/plexpy/graphs.py +++ b/plexpy/graphs.py @@ -436,16 +436,16 @@ class Graphs(object): 'SUM(CASE WHEN media_type = "season" THEN 1 ELSE 0 END) AS season_count, ' \ 'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ 'FROM library_stats_items ' \ - 'WHERE added_at < %s ' \ + 'WHERE updated_at < %s ' \ 'UNION ALL ' \ 'SELECT raM.date_added, ' \ 'SUM(CASE WHEN raM.media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \ '0 AS tv_count, ' \ '0 AS season_count, ' \ 'SUM(CASE WHEN raM.media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ - 'FROM (SELECT *, date(added_at, "unixepoch", "localtime") AS date_added ' \ + 'FROM (SELECT *, date(updated_at, "unixepoch", "localtime") AS date_added ' \ ' FROM library_stats_items ' \ - ' WHERE (media_type = "movie" OR media_type = "episode") AND added_at >= %s) AS raM ' \ + ' WHERE (media_type = "movie" OR media_type = "episode") AND updated_at >= %s) AS raM ' \ 'GROUP BY raM.date_added ' \ 'UNION ALL ' \ 'SELECT raG.date_added, ' \ @@ -453,9 +453,9 @@ class Graphs(object): 'SUM(CASE WHEN NOT raG.grandparent_rating_key = "" THEN 1 ELSE 0 END) AS tv_count, ' \ '0 AS season_count, ' \ '0 AS episode_count ' \ - 'FROM (SELECT *, date(added_at, "unixepoch", "localtime") AS date_added ' \ + 'FROM (SELECT *, date(updated_at, "unixepoch", "localtime") AS date_added ' \ ' FROM library_stats_items ' \ - ' WHERE NOT grandparent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ + ' WHERE NOT grandparent_rating_key = "" AND media_type = "episode" AND updated_at >= %s ' \ ' GROUP BY grandparent_rating_key) AS raG ' \ 'GROUP BY raG.date_added ' \ 'UNION ALL ' \ @@ -464,9 +464,9 @@ class Graphs(object): '0 AS tv_count, ' \ 'SUM(CASE WHEN NOT raS.parent_rating_key = "" THEN 1 ELSE 0 END) AS season_count, ' \ '0 AS episode_count ' \ - 'FROM (SELECT *, date(added_at, "unixepoch", "localtime") AS date_added ' \ + 'FROM (SELECT *, date(updated_at, "unixepoch", "localtime") AS date_added ' \ ' FROM library_stats_items ' \ - ' WHERE NOT parent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ + ' WHERE NOT parent_rating_key = "" AND media_type = "episode" AND updated_at >= %s ' \ ' GROUP BY parent_rating_key) AS raS ' \ 'GROUP BY raS.date_added ' \ 'ORDER BY date_added' % (timestamp, timestamp, timestamp, timestamp) @@ -478,9 +478,9 @@ class Graphs(object): '0 AS tv_count, ' \ '0 AS season_count, ' \ 'SUM(CASE WHEN raM.media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ - 'FROM (SELECT *, date(added_at, "unixepoch", "localtime") AS date_added ' \ - ' FROM recently_added ' \ - ' WHERE (media_type = "movie" OR media_type = "episode") AND added_at >= %s) AS raM ' \ + 'FROM (SELECT *, date(updated_at, "unixepoch", "localtime") AS date_added ' \ + ' FROM library_stats_items ' \ + ' WHERE (media_type = "movie" OR media_type = "episode") AND updated_at >= %s) AS raM ' \ 'GROUP BY raM.date_added ' \ 'UNION ALL ' \ 'SELECT raG.date_added, ' \ @@ -488,9 +488,9 @@ class Graphs(object): 'SUM(CASE WHEN NOT raG.grandparent_rating_key = "" THEN 1 ELSE 0 END) AS tv_count, ' \ '0 AS season_count, ' \ '0 AS episode_count ' \ - 'FROM (SELECT *, date(added_at, "unixepoch", "localtime") AS date_added ' \ - ' FROM recently_added ' \ - ' WHERE NOT grandparent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ + 'FROM (SELECT *, date(updated_at, "unixepoch", "localtime") AS date_added ' \ + ' FROM library_stats_items ' \ + ' WHERE NOT grandparent_rating_key = "" AND media_type = "episode" AND updated_at >= %s ' \ ' GROUP BY grandparent_rating_key) AS raG ' \ 'GROUP BY raG.date_added ' \ 'UNION ALL ' \ @@ -499,9 +499,9 @@ class Graphs(object): '0 AS tv_count, ' \ 'SUM(CASE WHEN NOT raS.parent_rating_key = "" THEN 1 ELSE 0 END) AS season_count, ' \ '0 AS episode_count ' \ - 'FROM (SELECT *, date(added_at, "unixepoch", "localtime") AS date_added ' \ - ' FROM recently_added ' \ - ' WHERE NOT parent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ + 'FROM (SELECT *, date(updated_at, "unixepoch", "localtime") AS date_added ' \ + ' FROM library_stats_items ' \ + ' WHERE NOT parent_rating_key = "" AND media_type = "episode" AND updated_at >= %s ' \ ' GROUP BY parent_rating_key) AS raS ' \ 'GROUP BY raS.date_added ' \ 'ORDER BY date_added' % (timestamp, timestamp, timestamp) @@ -596,7 +596,7 @@ class Graphs(object): 'SUM(CASE WHEN media_type = "season" THEN 1 ELSE 0 END) AS season_count, ' \ 'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ 'FROM library_stats_items ' \ - 'WHERE added_at >= %s' % timestamp + 'WHERE updated_at >= %s' % timestamp result = monitor_db.select(query) @@ -675,7 +675,7 @@ class Graphs(object): 'SUM(CASE WHEN raM.media_type = "episode" THEN 1 ELSE 0 END) AS episode_count ' \ 'FROM (SELECT *, %s ' \ 'FROM library_stats_items ' \ - 'WHERE (media_type = "movie" OR media_type = "episode") AND added_at >= %s) AS raM ' \ + 'WHERE (media_type = "movie" OR media_type = "episode") AND updated_at >= %s) AS raM ' \ 'GROUP BY raM.resolution ' \ 'UNION ALL ' \ 'SELECT ' \ @@ -686,7 +686,7 @@ class Graphs(object): '0 AS episode_count ' \ 'FROM (SELECT *, %s ' \ 'FROM library_stats_items ' \ - ' WHERE NOT grandparent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ + ' WHERE NOT grandparent_rating_key = "" AND media_type = "episode" AND updated_at >= %s ' \ ' GROUP BY grandparent_rating_key) AS raG ' \ 'GROUP BY raG.resolution ' \ 'UNION ALL ' \ @@ -698,7 +698,7 @@ class Graphs(object): '0 AS episode_count ' \ 'FROM (SELECT *, %s ' \ 'FROM library_stats_items ' \ - 'WHERE NOT parent_rating_key = "" AND media_type = "episode" AND added_at >= %s ' \ + 'WHERE NOT parent_rating_key = "" AND media_type = "episode" AND updated_at >= %s ' \ 'GROUP BY parent_rating_key) AS raS ' \ 'GROUP BY raS.resolution) AS ra ' \ 'GROUP BY resolution ' \ diff --git a/plexpy/libraries.py b/plexpy/libraries.py index 29198126..ec3f2ddd 100644 --- a/plexpy/libraries.py +++ b/plexpy/libraries.py @@ -66,14 +66,10 @@ def refresh_libraries(): library_keys = [] new_keys = [] - ratingKeys = [] # Keep track of section_id to update is_active status section_ids = [common.LIVE_TV_SECTION_ID] # Live TV library always considered active - _pms = pmsconnect.PmsConnect() - _datafactory = datafactory.DataFactory() - for section in library_sections: section_ids.append(helpers.cast_to_int(section['section_id'])) From 0b4c73395f1382916f107584c801e0e3cd569502 Mon Sep 17 00:00:00 2001 From: herby2212 Date: Sat, 21 Aug 2021 19:04:07 +0200 Subject: [PATCH 21/31] bug fixing and robustness improvements --- data/interfaces/default/graphs.html | 50 ++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index 8bf9b57d..52ec27ac 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -296,6 +296,7 @@ - + - - + + @@ -806,7 +806,7 @@ $('#months-selection').hide(); // Fixed as graph uses own measurement 'addition count' with formatting of 'plays' - setGraphFormat("plays"); + setGraphFormat("plays", "ddd D MMM YY"); $('#user-selection').hide(); $('#yaxis-selection').hide(); @@ -822,18 +822,18 @@ // Highlight the weekend if ((moment(day, 'YYYY-MM-DD').format('ddd') === 'Sat') || (moment(day, 'YYYY-MM-DD').format('ddd') === 'Sun')) { - hc_additions_by_day_options.xAxis.plotBands.push({ + hc_library_additions_by_day_options.xAxis.plotBands.push({ from: i-0.5, to: i+0.5, color: 'rgba(80,80,80,0.3)' }); } }); - hc_additions_by_day_options.yAxis.min = 0; - hc_additions_by_day_options.xAxis.categories = dateArray; - hc_additions_by_day_options.series = getGraphVisibility(hc_additions_by_day_options.chart.renderTo, data.series); - hc_additions_by_day_options.colors = getGraphColors(data.series); - var hc_additions_by_day = new Highcharts.Chart(hc_additions_by_day_options); + hc_library_additions_by_day_options.yAxis.min = 0; + hc_library_additions_by_day_options.xAxis.categories = dateArray; + hc_library_additions_by_day_options.series = getGraphVisibility(hc_library_additions_by_day_options.chart.renderTo, data.series); + hc_library_additions_by_day_options.colors = getGraphColors(data.series); + const hc_library_additions_by_day = new Highcharts.Chart(hc_library_additions_by_day_options); } }); @@ -861,7 +861,7 @@ hc_library_growth_by_day_options.xAxis.categories = dateArray; hc_library_growth_by_day_options.series = getGraphVisibility(hc_library_growth_by_day_options.chart.renderTo, data.series); hc_library_growth_by_day_options.colors = getGraphColors(data.series); - var hc_library_growth_by_day = new Highcharts.Chart(hc_library_growth_by_day_options); + const hc_library_growth_by_day = new Highcharts.Chart(hc_library_growth_by_day_options); } }); @@ -871,10 +871,10 @@ data: { time_range: time_range }, dataType: "json", success: function(data) { - hc_additions_by_media_type_options.xAxis.categories = data.categories; - hc_additions_by_media_type_options.series = getGraphVisibility(hc_additions_by_media_type_options.chart.renderTo, data.series); - hc_additions_by_media_type_options.colors = getGraphColors(data.series); - var hc_additions_by_media_type = new Highcharts.Chart(hc_additions_by_media_type_options); + hc_library_additions_by_media_type_options.xAxis.categories = data.categories; + hc_library_additions_by_media_type_options.series = getGraphVisibility(hc_library_additions_by_media_type_options.chart.renderTo, data.series); + hc_library_additions_by_media_type_options.colors = getGraphColors(data.series); + const hc_library_additions_by_media_type = new Highcharts.Chart(hc_library_additions_by_media_type_options); } }); @@ -884,10 +884,10 @@ data: { time_range: time_range }, dataType: "json", success: function(data) { - hc_additions_by_source_resolution_options.xAxis.categories = data.categories; - hc_additions_by_source_resolution_options.series = getGraphVisibility(hc_additions_by_source_resolution_options.chart.renderTo, data.series); - hc_additions_by_source_resolution_options.colors = getGraphColors(data.series); - var hc_additions_by_source_resolution = new Highcharts.Chart(hc_additions_by_source_resolution_options); + hc_library_additions_by_source_resolution_options.xAxis.categories = data.categories; + hc_library_additions_by_source_resolution_options.series = getGraphVisibility(hc_library_additions_by_source_resolution_options.chart.renderTo, data.series); + hc_library_additions_by_source_resolution_options.colors = getGraphColors(data.series); + const hc_library_additions_by_source_resolution = new Highcharts.Chart(hc_library_additions_by_source_resolution_options); } }); @@ -1000,12 +1000,12 @@ //GraphTab4 not needed as Addition Count is used for the Y-Axis }); - function setGraphFormat(type) { + function setGraphFormat(type, tooltipFormat) { if (type === 'plays') { yaxis_format = function() { return this.value; }; tooltip_format = function() { if (moment(this.x, 'X').isValid() && (this.x > 946684800)) { - var s = ''+ moment(this.x).format('ddd MMM D') +''; + var s = tooltipFormat ? ''+ moment(this.x).format(tooltipFormat) +'' : ''+ moment(this.x).format('ddd MMM D') +''; } else { var s = ''+ this.x +''; } @@ -1057,7 +1057,7 @@ } hc_plays_by_day_options.xAxis.plotBands = []; - hc_additions_by_day_options.xAxis.plotBands = []; + hc_library_additions_by_day_options.xAxis.plotBands = []; hc_library_growth_by_day_options.xAxis.plotBands = []; hc_plays_by_stream_type_options.xAxis.plotBands = []; @@ -1070,14 +1070,14 @@ hc_plays_by_source_resolution_options.yAxis.labels.formatter = yaxis_format; hc_plays_by_stream_resolution_options.yAxis.labels.formatter = yaxis_format; hc_plays_by_platform_by_stream_type_options.yAxis.labels.formatter = yaxis_format; - hc_additions_by_day_options.yAxis[0].labels.formatter = yaxis_format; - hc_additions_by_day_options.yAxis[1].labels.formatter = yaxis_format; - hc_additions_by_day_options.yAxis[2].labels.formatter = yaxis_format; + hc_library_additions_by_day_options.yAxis[0].labels.formatter = yaxis_format; + hc_library_additions_by_day_options.yAxis[1].labels.formatter = yaxis_format; + hc_library_additions_by_day_options.yAxis[2].labels.formatter = yaxis_format; hc_library_growth_by_day_options.yAxis[0].labels.formatter = yaxis_format; hc_library_growth_by_day_options.yAxis[1].labels.formatter = yaxis_format; hc_library_growth_by_day_options.yAxis[2].labels.formatter = yaxis_format; - hc_additions_by_media_type_options.yAxis.labels.formatter = yaxis_format; - hc_additions_by_source_resolution_options.yAxis.labels.formatter = yaxis_format; + hc_library_additions_by_media_type_options.yAxis.labels.formatter = yaxis_format; + hc_library_additions_by_source_resolution_options.yAxis.labels.formatter = yaxis_format; hc_plays_by_user_by_stream_type_options.yAxis.labels.formatter = yaxis_format; hc_plays_by_month_options.yAxis.labels.formatter = yaxis_format; @@ -1085,9 +1085,9 @@ hc_plays_by_dayofweek_options.tooltip.formatter = tooltip_format; hc_plays_by_hourofday_options.tooltip.formatter = tooltip_format; hc_plays_by_platform_options.tooltip.formatter = tooltip_format; - hc_additions_by_media_type_options.tooltip.formatter = tooltip_format; - hc_additions_by_source_resolution_options.tooltip.formatter = tooltip_format; - hc_additions_by_day_options.tooltip.formatter = tooltip_format; + hc_library_additions_by_media_type_options.tooltip.formatter = tooltip_format; + hc_library_additions_by_source_resolution_options.tooltip.formatter = tooltip_format; + hc_library_additions_by_day_options.tooltip.formatter = tooltip_format; hc_library_growth_by_day_options.tooltip.formatter = tooltip_format; hc_plays_by_user_options.tooltip.formatter = tooltip_format; hc_plays_by_stream_type_options.tooltip.formatter = tooltip_format; diff --git a/data/interfaces/default/js/graphs/additions_by_day.js b/data/interfaces/default/js/graphs/library_additions_by_day.js similarity index 94% rename from data/interfaces/default/js/graphs/additions_by_day.js rename to data/interfaces/default/js/graphs/library_additions_by_day.js index 75a11551..502cc35f 100644 --- a/data/interfaces/default/js/graphs/additions_by_day.js +++ b/data/interfaces/default/js/graphs/library_additions_by_day.js @@ -1,4 +1,4 @@ -var hc_additions_by_day_options = { +var hc_library_additions_by_day_options = { chart: { type: 'line', backgroundColor: 'rgba(0,0,0,0)', @@ -39,7 +39,7 @@ var hc_additions_by_day_options = { type: 'datetime', labels: { formatter: function() { - return moment(this.value).format("MMM D"); + return moment(this.value).format("YY MMM D"); }, style: { color: '#aaa' diff --git a/data/interfaces/default/js/graphs/additions_by_media_type.js b/data/interfaces/default/js/graphs/library_additions_by_media_type.js similarity index 96% rename from data/interfaces/default/js/graphs/additions_by_media_type.js rename to data/interfaces/default/js/graphs/library_additions_by_media_type.js index 8a25910c..556fce5b 100644 --- a/data/interfaces/default/js/graphs/additions_by_media_type.js +++ b/data/interfaces/default/js/graphs/library_additions_by_media_type.js @@ -1,4 +1,4 @@ -var hc_additions_by_media_type_options = { +var hc_library_additions_by_media_type_options = { chart: { type: 'column', backgroundColor: 'rgba(0,0,0,0)', diff --git a/data/interfaces/default/js/graphs/additions_by_source_resolution.js b/data/interfaces/default/js/graphs/library_additions_by_source_resolution.js similarity index 96% rename from data/interfaces/default/js/graphs/additions_by_source_resolution.js rename to data/interfaces/default/js/graphs/library_additions_by_source_resolution.js index 25dcfaaa..dc106dc4 100644 --- a/data/interfaces/default/js/graphs/additions_by_source_resolution.js +++ b/data/interfaces/default/js/graphs/library_additions_by_source_resolution.js @@ -1,4 +1,4 @@ -var hc_additions_by_source_resolution_options = { +var hc_library_additions_by_source_resolution_options = { chart: { type: 'column', backgroundColor: 'rgba(0,0,0,0)', From 6d7b9a566eb6f84fc42dd09f7ff917e304836914 Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Sat, 7 Jan 2023 18:06:37 +0100 Subject: [PATCH 29/31] tooltip und points fadeout for synced graphs + fix --- data/interfaces/default/graphs.html | 51 +++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index 8e40caf4..36328230 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -406,7 +406,7 @@ // Sync selected day (tooltip position) // https://jsfiddle.net/BlackLabel/5wq9sdbp - ['mousemove', 'touchmove', 'touchstart'].forEach(function (eventType) { + ['mousemove', 'touchmove', 'touchstart', 'mouseleave'].forEach(function (eventType) { document.getElementById('section-library-graphs').addEventListener( eventType, function(e) { @@ -424,36 +424,53 @@ for (i = 0; i < Highcharts.charts.length; i = i + 1) { chart = _Charts[i]; - if(chart.options.chart.type != "line" || !syncLinks.hasOwnProperty(chart.renderTo.id)) { + if(chart == undefined || chart.options.chart.type != "line" || !syncLinks.hasOwnProperty(chart.renderTo.id)) { continue; } // Find coordinates within the chart event = chart.pointer.normalize(e); - + const isMouseLeave = e.type == "mouseleave"; otherChart = _Charts.find(c => c['renderTo'].id == syncLinks[chart.renderTo.id]); // Get the hovered points points = []; chart.series.forEach(function (series, idx) { var _point = series.searchPoint(event, true); - if(_point && _point.series.visible == true) { - points.push(_point); + if(isMouseLeave) { + if(_point) { + _point.setState(''); + points.push(_point); + } + } else { + if(_point && _point.series.visible == true) { + points.push(_point); + } } }); - - if(points.length && !points.includes(undefined)) { - chart.tooltip.refresh(points); - chart.xAxis[0].drawCrosshair(e, points[0]); - //chart.xAxis[0].drawCrosshair(e, points[points.length]); - pointsChart2 = [] + if(points.length && !points.includes(undefined)) { number = 0; + if(isMouseLeave) { + chart.tooltip.hide(); + chart.xAxis[0].hideCrosshair(); + } else { + chart.tooltip.refresh(points); + chart.xAxis[0].drawCrosshair(e, points[0]); + //chart.xAxis[0].drawCrosshair(e, points[points.length]); + + pointsChart2 = [] + } + otherChart.series.forEach(function(_series, idx) { if(_series.visible) { try { var _point = _series.points[points[number].x]; - pointsChart2.push(_point); + if(isMouseLeave) { + _point.setState(''); + } else { + pointsChart2.push(_point); + } number++; } catch { // Graph render issue. Normalizes by itself so no additional measures required. @@ -461,8 +478,14 @@ } } }); - otherChart.tooltip.refresh(pointsChart2); - otherChart.xAxis[0].drawCrosshair(e, pointsChart2[0]); + + if(isMouseLeave) { + otherChart.tooltip.hide(); + otherChart.xAxis[0].hideCrosshair(); + } else { + otherChart.tooltip.refresh(pointsChart2); + otherChart.xAxis[0].drawCrosshair(e, pointsChart2[0]); + } } /** From 95670fb84a277a3b4707f74a514cd96924fa1995 Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Sat, 7 Jan 2023 18:11:52 +0100 Subject: [PATCH 30/31] code update for conform structure --- data/interfaces/default/graphs.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index 36328230..83377d72 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -827,11 +827,11 @@ function loadGraphsTab4(time_range, yaxis) { $('#days-selection').show(); $('#months-selection').hide(); + $('#user-selection').hide(); + $('#yaxis-selection').hide(); // Fixed as graph uses own measurement 'addition count' with formatting of 'plays' setGraphFormat("plays", "ddd D MMM YY"); - $('#user-selection').hide(); - $('#yaxis-selection').hide(); var _graph_1_call = $.ajax({ url: "get_additions_by_date", From 0cc69c275b237c3b1d56fc1600ede03a6b996ecf Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Sun, 23 Jul 2023 19:36:38 +0200 Subject: [PATCH 31/31] update config --- plexpy/config.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plexpy/config.py b/plexpy/config.py index 8ac0c0a0..001feaf2 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -300,6 +300,7 @@ SETTINGS = [ 'PMS_VERSION', 'PMS_WEB_URL', 'REFRESH_LIBRARIES_INTERVAL', + 'REFRESH_LIBRARY_STATS_DATA_INTERVAL', 'REFRESH_USERS_INTERVAL', 'SHOW_ADVANCED_SETTINGS', 'TIME_FORMAT', @@ -339,6 +340,7 @@ CHECKED_SETTINGS = [ 'PLEXPY_AUTO_UPDATE', 'PMS_URL_MANUAL', 'REFRESH_LIBRARIES_ON_STARTUP', + 'REFRESH_LIBRARY_STATS_DATA_ON_STARTUP', 'REFRESH_USERS_ON_STARTUP', 'SYS_TRAY_ICON', 'THEMOVIEDB_LOOKUP', @@ -710,3 +712,8 @@ class Config(object): self.ANON_REDIRECT_DYNAMIC = 1 self.CONFIG_VERSION = 22 + if self.CONFIG_VERSION == 22: + self.REFRESH_LIBRARY_STATS_DATA_INTERVAL = 12 + self.REFRESH_LIBRARY_STATS_DATA_ON_STARTUP = 1 + + self.CONFIG_VERSION = 23