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;