Skip to content

Commit a9707a0

Browse files
authored
Re-enable drilldown into daily statistics (#1)
- Page views are aggregated per day (based on the CMS timezone) and archived as an OrchardCore document to save space. - Background task automatically aggregates the page views to the document every day - Cleanup code and remove orphaned classes and properties
1 parent 1287000 commit a9707a0

38 files changed

Lines changed: 1158 additions & 717 deletions
Lines changed: 86 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,106 @@
1-
namespace Neolution.OrchardCoreModules.PageViewStats.Controllers
1+
namespace Neolution.OrchardCoreModules.PageViewStats.Controllers;
2+
3+
using System;
4+
using System.Threading.Tasks;
5+
using Dapper;
6+
using Microsoft.AspNetCore.Mvc;
7+
using Neolution.OrchardCoreModules.PageViewStats.Models;
8+
using Neolution.OrchardCoreModules.PageViewStats.Services;
9+
using Neolution.OrchardCoreModules.PageViewStats.Settings;
10+
using OrchardCore.Data;
11+
using OrchardCore.Entities;
12+
using OrchardCore.Settings;
13+
using YesSql;
14+
15+
public class CountPageViewController : Controller
216
{
3-
using System;
4-
using System.Text;
5-
using System.Threading.Tasks;
6-
using Dapper;
7-
using Microsoft.AspNetCore.Mvc;
8-
using Neolution.OrchardCoreModules.PageViewStats.Models;
9-
using Neolution.OrchardCoreModules.PageViewStats.Services;
10-
using OrchardCore.Data;
11-
using OrchardCore.Entities;
12-
using OrchardCore.Settings;
13-
using YesSql;
17+
/// <summary>
18+
/// The table for storing the page views
19+
/// </summary>
20+
private string table;
21+
22+
/// <summary>
23+
/// The SQL dialect
24+
/// </summary>
25+
private ISqlDialect dialect;
1426

15-
public class CountPageViewController : Controller
27+
private readonly ISession session;
28+
private readonly IDbConnectionAccessor dbConnectionAccessor;
29+
private readonly ISiteService siteService;
30+
private readonly IBotDetector botDetector;
31+
32+
public CountPageViewController(ISession session, IDbConnectionAccessor dbConnectionAccessor, ISiteService siteService, IBotDetector botDetector)
1633
{
17-
/// <summary>
18-
/// The table for storing the page views
19-
/// </summary>
20-
private string table;
34+
this.session = session;
35+
this.dbConnectionAccessor = dbConnectionAccessor;
36+
this.siteService = siteService;
37+
this.botDetector = botDetector;
2138

22-
/// <summary>
23-
/// The SQL dialect
24-
/// </summary>
25-
private ISqlDialect dialect;
39+
SetupDatabaseFields();
40+
}
2641

27-
private readonly ISession session;
28-
private readonly IDbConnectionAccessor dbConnectionAccessor;
29-
private readonly ISiteService siteService;
30-
private readonly IBotDetector botDetector;
42+
[HttpPost]
43+
[ActionName(nameof(Index))]
44+
public async Task<ActionResult> IndexPost(string contentItemId)
45+
{
46+
var settings = (await siteService.GetSiteSettingsAsync()).As<PageViewStatsSettings>();
3147

32-
public CountPageViewController(ISession session, IDbConnectionAccessor dbConnectionAccessor, ISiteService siteService, IBotDetector botDetector)
48+
if (!settings.IsEnabled)
3349
{
34-
this.session = session;
35-
this.dbConnectionAccessor = dbConnectionAccessor;
36-
this.siteService = siteService;
37-
this.botDetector = botDetector;
38-
39-
SetupDatabaseFields();
50+
// Ignore requests when page view statistics are disabled
51+
return Ok();
4052
}
4153

42-
[HttpPost]
43-
[ActionName(nameof(Index))]
44-
public async Task<ActionResult> IndexPost(string contentItemId)
54+
var pageView = new PageView
4555
{
46-
var settings = (await siteService.GetSiteSettingsAsync()).As<PageViewStatsSettings>();
47-
48-
if (!settings.IsEnabled)
49-
{
50-
// Ignore requests when page view statistics are disabled
51-
return Ok();
52-
}
53-
54-
var pageView = new PageView
55-
{
56-
ContentItemId = contentItemId,
57-
CreatedUtc = DateTimeOffset.UtcNow,
58-
};
56+
ContentItemId = contentItemId,
57+
CreatedUtc = DateTimeOffset.UtcNow,
58+
};
5959

60-
if (settings.CollectUserIp)
61-
{
62-
pageView.RequestIpAddress = this.HttpContext.Connection.RemoteIpAddress?.ToString();
63-
}
60+
if (settings.CollectUserIp)
61+
{
62+
pageView.RequestIpAddress = this.HttpContext.Connection.RemoteIpAddress?.ToString();
63+
}
6464

65-
if (settings.CollectUserAgentString)
66-
{
67-
pageView.RequestUserAgentString = this.Request.Headers.UserAgent.ToString();
68-
pageView.RequestUserAgentIsRobot = this.botDetector.CheckUserAgentString(pageView.RequestUserAgentString);
69-
}
65+
if (settings.CollectUserAgentString)
66+
{
67+
pageView.RequestUserAgentString = this.Request.Headers.UserAgent.ToString();
68+
pageView.RequestUserAgentIsRobot = this.botDetector.CheckUserAgentString(pageView.RequestUserAgentString);
69+
}
7070

71-
await using var connection = this.dbConnectionAccessor.CreateConnection();
71+
await using var connection = this.dbConnectionAccessor.CreateConnection();
7272

73-
var insertCmd =
74-
$"INSERT INTO {dialect.QuoteForTableName(table)} (" +
75-
$" {dialect.QuoteForColumnName(nameof(PageView.Id))}, " +
76-
$" {dialect.QuoteForColumnName(nameof(PageView.CreatedUtc))}, " +
77-
$" {dialect.QuoteForColumnName(nameof(PageView.ContentItemId))}, " +
78-
$" {dialect.QuoteForColumnName(nameof(PageView.RequestIpAddress))}, " +
79-
$" {dialect.QuoteForColumnName(nameof(PageView.RequestUserAgentString))}, " +
80-
$" {dialect.QuoteForColumnName(nameof(PageView.RequestUserAgentIsRobot))}) " +
81-
$"VALUES (" +
82-
$" @{nameof(PageView.Id)}, " +
83-
$" @{nameof(PageView.CreatedUtc)}, " +
84-
$" @{nameof(PageView.ContentItemId)}, " +
85-
$" @{nameof(PageView.RequestIpAddress)}, " +
86-
$" @{nameof(PageView.RequestUserAgentString)}, " +
87-
$" @{nameof(PageView.RequestUserAgentIsRobot)});";
73+
var insertCmd =
74+
$"INSERT INTO {dialect.QuoteForTableName(table)} (" +
75+
$" {dialect.QuoteForColumnName(nameof(PageView.Id))}, " +
76+
$" {dialect.QuoteForColumnName(nameof(PageView.CreatedUtc))}, " +
77+
$" {dialect.QuoteForColumnName(nameof(PageView.ContentItemId))}, " +
78+
$" {dialect.QuoteForColumnName(nameof(PageView.RequestIpAddress))}, " +
79+
$" {dialect.QuoteForColumnName(nameof(PageView.RequestUserAgentString))}, " +
80+
$" {dialect.QuoteForColumnName(nameof(PageView.RequestUserAgentIsRobot))}) " +
81+
$"VALUES (" +
82+
$" @{nameof(PageView.Id)}, " +
83+
$" @{nameof(PageView.CreatedUtc)}, " +
84+
$" @{nameof(PageView.ContentItemId)}, " +
85+
$" @{nameof(PageView.RequestIpAddress)}, " +
86+
$" @{nameof(PageView.RequestUserAgentString)}, " +
87+
$" @{nameof(PageView.RequestUserAgentIsRobot)});";
8888

89-
await connection.ExecuteAsync(insertCmd, pageView);
90-
91-
return Ok();
92-
}
89+
await connection.ExecuteAsync(insertCmd, pageView);
9390

94-
private void SetupDatabaseFields()
95-
{
96-
dialect = session.Store.Configuration.SqlDialect;
91+
return Ok();
92+
}
9793

98-
var tablePrefix = session.Store.Configuration.TablePrefix;
99-
if (!string.IsNullOrEmpty(tablePrefix))
100-
{
101-
tablePrefix += '_';
102-
}
94+
private void SetupDatabaseFields()
95+
{
96+
dialect = session.Store.Configuration.SqlDialect;
10397

104-
table = $"{tablePrefix}{PageView.TableName}";
98+
var tablePrefix = session.Store.Configuration.TablePrefix;
99+
if (!string.IsNullOrEmpty(tablePrefix))
100+
{
101+
tablePrefix += '_';
105102
}
103+
104+
table = $"{tablePrefix}{PageView.TableName}";
106105
}
107-
}
106+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
namespace Neolution.OrchardCoreModules.PageViewStats.Controllers;
2+
3+
using System;
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Authorization;
6+
using Microsoft.AspNetCore.Mvc;
7+
using Neolution.OrchardCoreModules.PageViewStats.Models;
8+
using Neolution.OrchardCoreModules.PageViewStats.Services;
9+
using Neolution.OrchardCoreModules.PageViewStats.ViewModels;
10+
using OrchardCore.Admin;
11+
using OrchardCore.ContentManagement;
12+
using OrchardCore.Data;
13+
using OrchardCore.Settings;
14+
using YesSql;
15+
16+
[Admin]
17+
public class DashboardController : Controller
18+
{
19+
private readonly ISession session;
20+
private readonly IDbConnectionAccessor dbConnectionAccessor;
21+
private readonly IAuthorizationService authorizationService;
22+
private readonly IContentManager contentManager;
23+
private readonly ISiteService siteService;
24+
private readonly IPageViewsRepository repository;
25+
26+
private ISqlDialect dialect;
27+
private string table;
28+
29+
public DashboardController(ISession session, IDbConnectionAccessor dbConnectionAccessor, IAuthorizationService authorizationService, IContentManager contentManager, ISiteService siteService, IPageViewsRepository repository)
30+
{
31+
this.session = session;
32+
this.dbConnectionAccessor = dbConnectionAccessor;
33+
this.authorizationService = authorizationService;
34+
this.contentManager = contentManager;
35+
this.siteService = siteService;
36+
this.repository = repository;
37+
38+
SetupDatabaseFields();
39+
}
40+
41+
[HttpGet]
42+
public async Task<ActionResult> Index(int history)
43+
{
44+
if (!await this.authorizationService.AuthorizeAsync(User, PageViewStatsPermissions.ViewPageViewStats))
45+
{
46+
return Forbid();
47+
}
48+
49+
if (history > 30 || history <= 0)
50+
{
51+
// Limit history to maximum number of 30 days
52+
history = 30;
53+
}
54+
55+
var settings = await this.siteService.GetSiteSettingsAsync();
56+
var tz = TimeZoneInfo.FindSystemTimeZoneById(settings.TimeZoneId);
57+
var now = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tz);
58+
var today = DateOnly.FromDateTime(now);
59+
60+
var pageViews = await this.repository.LoadPageViewsAsync(today.AddDays(history * -1), today);
61+
62+
var viewModel = new DashboardViewModel { History = history, PageViews = pageViews };
63+
64+
return View(viewModel);
65+
}
66+
67+
private void SetupDatabaseFields()
68+
{
69+
dialect = session.Store.Configuration.SqlDialect;
70+
71+
var tablePrefix = session.Store.Configuration.TablePrefix;
72+
if (!string.IsNullOrEmpty(tablePrefix))
73+
{
74+
tablePrefix += '_';
75+
}
76+
77+
table = $"{tablePrefix}{PageView.TableName}";
78+
}
79+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
namespace Neolution.OrchardCoreModules.PageViewStats.Controllers;
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
using MediatR;
8+
using Microsoft.AspNetCore.Authorization;
9+
using Microsoft.AspNetCore.Mvc;
10+
using Neolution.OrchardCoreModules.PageViewStats.Queries;
11+
using Neolution.OrchardCoreModules.PageViewStats.ViewModels;
12+
using OrchardCore.Admin;
13+
using OrchardCore.Autoroute.Models;
14+
using OrchardCore.ContentManagement;
15+
16+
[Admin]
17+
public class DayController : Controller
18+
{
19+
private readonly IAuthorizationService authorizationService;
20+
private readonly IContentManager contentManager;
21+
private readonly ISender sender;
22+
23+
public DayController(IAuthorizationService authorizationService, IContentManager contentManager, ISender sender)
24+
{
25+
this.authorizationService = authorizationService;
26+
this.contentManager = contentManager;
27+
this.sender = sender;
28+
}
29+
30+
[HttpGet]
31+
public async Task<ActionResult> Index(DateTime day)
32+
{
33+
if (!await this.authorizationService.AuthorizeAsync(User, PageViewStatsPermissions.ViewPageViewStats))
34+
{
35+
return Forbid();
36+
}
37+
38+
var date = DateOnly.FromDateTime(day);
39+
40+
var pageViews = await this.sender.Send(new GetPageViewsPerDayQuery(date));
41+
42+
var contentItems = await this.contentManager.GetAsync(pageViews.Select(x => x.ContentItemId));
43+
44+
var viewModel = new DayIndexViewModel
45+
{
46+
Date = date,
47+
Items = new List<PageViewsPerContentItem>()
48+
};
49+
50+
foreach (var contentItem in contentItems)
51+
{
52+
var pageView = pageViews.FirstOrDefault(x => x.ContentItemId == contentItem.ContentItemId);
53+
if (pageView == null)
54+
{
55+
continue;
56+
}
57+
58+
var item = new PageViewsPerContentItem
59+
{
60+
ContentItemId = contentItem.ContentItemId,
61+
DisplayText = contentItem.DisplayText,
62+
Route = contentItem.Get<AutoroutePart>("AutoroutePart"),
63+
TotalViews = pageView.TotalViews,
64+
BotViews = pageView.BotViews,
65+
UniqueVisitors = pageView.UniqueVisitors,
66+
};
67+
68+
viewModel.Items.Add(item);
69+
}
70+
71+
return View(viewModel);
72+
}
73+
}

0 commit comments

Comments
 (0)