diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index c76a490..8ce795e 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
- "@tabler/core": "^1.0.0-beta19",
+ "@tabler/core": "^1.0.0-beta20",
"apexcharts": "^3.36.3",
"htmx.org": "^1.9.2"
},
@@ -108,12 +108,13 @@
}
},
"node_modules/@tabler/core": {
- "version": "1.0.0-beta19",
- "resolved": "https://registry.npmjs.org/@tabler/core/-/core-1.0.0-beta19.tgz",
- "integrity": "sha512-iJWAuAWeekCxb7eGeM34Qwz32nlqykp0IeRn5W1z1hTCuU0arQknHphDs36ODxAiYwEXnY3yfKHOUvpW+Lr5Vg==",
+ "version": "1.0.0-beta20",
+ "resolved": "https://registry.npmjs.org/@tabler/core/-/core-1.0.0-beta20.tgz",
+ "integrity": "sha512-OzKpur+Ug7e+HMbNJrMcSuWZGUsJTvu7HYboBNRE8qyo1RKIWqvwL5YewKBJ+odW5pDOqBPzbsS4je3EBQQxHw==",
"dependencies": {
- "@popperjs/core": "^2.11.7",
- "bootstrap": "5.3.0-alpha3"
+ "@popperjs/core": "^2.11.8",
+ "@tabler/icons": "^2.32.0",
+ "bootstrap": "5.3.1"
},
"engines": {
"node": ">=18"
@@ -123,6 +124,7 @@
"url": "https://github.com/sponsors/codecalm"
},
"peerDependencies": {
+ "@melloware/coloris": "^0.19.1",
"apexcharts": "^3.40.0",
"autosize": "^6.0.1",
"choices.js": "^10.2.0",
@@ -141,6 +143,9 @@
"tom-select": "^2.2.2"
},
"peerDependenciesMeta": {
+ "@melloware/coloris": {
+ "optional": true
+ },
"apexcharts": {
"optional": true
},
@@ -191,6 +196,15 @@
}
}
},
+ "node_modules/@tabler/icons": {
+ "version": "2.41.0",
+ "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-2.41.0.tgz",
+ "integrity": "sha512-n7a4Qx/Narn4thV/FxnWb0GoOSGdFTA5ZRLuvgshXn3YxwP0Z7KLzgQ5tQs1gpwdNF5f+HI3dhVTfv/ZG6Zahw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/codecalm"
+ }
+ },
"node_modules/@types/estree": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
@@ -223,9 +237,9 @@
"dev": true
},
"node_modules/bootstrap": {
- "version": "5.3.0-alpha3",
- "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.0-alpha3.tgz",
- "integrity": "sha512-FBhOWMxkCFr74hesJdchLXhqagPTXS+kRNU3gE0FR5Ki/AdPSz32Ik96Z28+yBluCnE/pc9st7l1yPwKgbtfSA==",
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.1.tgz",
+ "integrity": "sha512-jzwza3Yagduci2x0rr9MeFSORjcHpt0lRZukZPZQJT1Dth5qzV7XcgGqYzi39KGAVYR8QEDVoO0ubFKOxzMG+g==",
"funding": [
{
"type": "github",
@@ -237,7 +251,7 @@
}
],
"peerDependencies": {
- "@popperjs/core": "^2.11.7"
+ "@popperjs/core": "^2.11.8"
}
},
"node_modules/brace-expansion": {
diff --git a/frontend/package.json b/frontend/package.json
index 8bb0e94..606d180 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -9,7 +9,7 @@
"author": "",
"license": "ISC",
"dependencies": {
- "@tabler/core": "^1.0.0-beta19",
+ "@tabler/core": "^1.0.0-beta20",
"apexcharts": "^3.36.3",
"htmx.org": "^1.9.2"
},
diff --git a/spybot/templates/spybot/base/navbar.html b/spybot/templates/spybot/base/navbar.html
index 9933d99..9355bbf 100644
--- a/spybot/templates/spybot/base/navbar.html
+++ b/spybot/templates/spybot/base/navbar.html
@@ -32,13 +32,13 @@
{% tabler_icon 'sun' %}
- {% if user != None %}
+ {% if logged_in_user != None %}
+
+
+
+ Longest streak
+
+
+ {{ streak.length }} days
+
+
diff --git a/spybot/views.py b/spybot/views.py
index f136636..44d2ba1 100644
--- a/spybot/views.py
+++ b/spybot/views.py
@@ -22,8 +22,12 @@ def get_user(request):
return request.user if isinstance(request.user, MergedUser) else None
+def get_context(request):
+ return {"logged_in_user": get_user(request)}
+
+
def home(request):
- context = {}
+ context = get_context(request)
# logged in user
user = get_user(request)
@@ -182,6 +186,7 @@ def convert_to_jstime(dt: datetime.datetime):
first_user_object["data"] = new_first_user_series
return render(request, 'spybot/timeline.html', {
+ **get_context(request),
'activity_by_user': list(users.values()),
'min': convert_to_jstime(cutoff),
'max': convert_to_jstime(now_time),
@@ -209,7 +214,7 @@ def halloffame(request):
merged_user['num_silver_awards'] = len(awards_silver)
merged_user['num_bronze_awards'] = len(awards_bronze)
- context = {'top_users': users}
+ context = {**get_context(request), 'top_users': users}
return render(request, 'spybot/halloffame.html', context)
@@ -220,6 +225,8 @@ def user(request, user_id: int):
afk_time = int(u.get('afk_time'))
online_time = int(u.get('online_time'))
+ streak = visualization.user_longest_streak(user_id)[0]
+
game_name = ""
game_id = 0
@@ -227,12 +234,14 @@ def user(request, user_id: int):
game_id, game_name = get_steam_game(MergedUser.objects.get(id=user_id))
return render(request, 'spybot/user.html', {
+ **get_context(request),
'user': u,
'total_time': afk_time + online_time,
'data': [afk_time, online_time],
'names': str(u.get('names')).split(","),
'game_id': game_id,
- 'game_name': game_name
+ 'game_name': game_name,
+ 'streak': streak,
})
@@ -290,4 +299,4 @@ def recent_events_fragment(request):
def profile(request):
user = get_user(request)
- return render(request, 'spybot/profile.html', {'user': user})
+ return render(request, 'spybot/profile.html', {**get_context(request), 'user': user})
diff --git a/spybot/visualization.py b/spybot/visualization.py
index 9cbb8b7..94f5cec 100644
--- a/spybot/visualization.py
+++ b/spybot/visualization.py
@@ -261,3 +261,80 @@ def user(user_id: int):
awards;""", [user_id, user_id])
return dictfetchall(cursor)
+
+
+def user_longest_streak(merged_user_id: int):
+ with connection.cursor() as cursor:
+ cursor.execute("""
+ WITH dates AS (
+ SELECT DISTINCT
+ spybot_mergeduser.name,
+ CAST(TSUserActivity.startTime AS DATE) AS day
+ FROM TSUserActivity
+ INNER JOIN TSUser ON tsUserID = TSUser.id
+ INNER JOIN spybot_mergeduser ON TSUser.merged_user_id = spybot_mergeduser.id
+ WHERE merged_user_id = %s
+ ),
+ cte AS (
+ SELECT
+ day,
+ IFNULL(DATE(day) > DATE(LAG(day, 1) OVER (ORDER BY day)) + INTERVAL 1 DAY, 1) AS startsStreak
+ FROM dates
+ ),
+ result AS (
+ SELECT
+ dates.day AS start_day,
+ SUM(startsStreak) AS streakGroup,
+ ROW_NUMBER() OVER (PARTITION BY SUM(startsStreak) ORDER BY dates.day) AS runningStreakLength,
+ COUNT(*) OVER (PARTITION BY SUM(startsStreak)) AS totalStreakLength
+ FROM
+ dates
+ JOIN cte ON dates.day >= cte.day AND cte.startsStreak = 1
+ GROUP BY dates.day
+ ORDER BY dates.day
+ )
+ SELECT
+ start_day,
+ DATE_ADD(start_day, INTERVAL (totalStreakLength - 1) DAY) AS end_day,
+ totalStreakLength AS length
+ FROM result
+ WHERE runningStreakLength = 1
+ ORDER BY totalStreakLength DESC, start_day DESC
+ LIMIT 1;
+ """, [merged_user_id])
+ return dictfetchall(cursor)
+
+
+#
+# WITH dates AS (
+# SELECT DISTINCT
+# CAST(TSUserActivity.startTime AS DATE) AS day
+# FROM TSUserActivity
+# ORDER BY day DESC
+# ),
+# cte AS (
+# SELECT
+# day,
+# IFNULL(DATE(day) > DATE(LAG(day, 1) OVER (ORDER BY day)) + INTERVAL 1 DAY, 1) AS startsStreak
+# FROM dates
+# ),
+# result AS (
+# SELECT
+# dates.day AS start_day,
+# SUM(startsStreak) AS streakGroup,
+# ROW_NUMBER() OVER (PARTITION BY SUM(startsStreak) ORDER BY dates.day) AS runningStreakLength,
+# COUNT(*) OVER (PARTITION BY SUM(startsStreak)) AS totalStreakLength
+# FROM
+# dates
+# JOIN cte ON dates.day >= cte.day AND cte.startsStreak = 1
+# GROUP BY dates.day
+# ORDER BY dates.day
+# )
+# SELECT
+# start_day,
+# DATE_ADD(start_day, INTERVAL (totalStreakLength - 1) DAY) AS end_day,
+# totalStreakLength AS streak_length
+# FROM result
+# WHERE runningStreakLength = 1
+# ORDER BY totalStreakLength DESC, start_day DESC
+# LIMIT 1;