Add time stamps to games in tables

This commit is contained in:
Maximilian Keßler 2023-12-26 11:54:40 +01:00
parent 498f460ccd
commit 41e8fb9c85
Signed by: max
GPG key ID: BCC5A619923C0BA5
5 changed files with 56 additions and 27 deletions

View file

@ -133,6 +133,8 @@ CREATE TABLE games (
seed TEXT NOT NULL, seed TEXT NOT NULL,
score SMALLINT NOT NULL, score SMALLINT NOT NULL,
num_turns SMALLINT NOT NULL, num_turns SMALLINT NOT NULL,
datetime_started TIMESTAMPTZ NOT NULL,
datetime_finished TIMESTAMPTZ NOT NULL,
/** /**
* This is the league id mentioned above that will represent the ordering of games regarding being processed by ELO. * This is the league id mentioned above that will represent the ordering of games regarding being processed by ELO.
* Note that this means when fetching new data from hanab.live, we have to fetch *all* of it and insert the games sorted * Note that this means when fetching new data from hanab.live, we have to fetch *all* of it and insert the games sorted

View file

@ -1,4 +1,5 @@
import json import json
from dataclasses import dataclass
from typing import Dict, List, Optional from typing import Dict, List, Optional
import platformdirs import platformdirs
@ -28,17 +29,18 @@ session = requests_cache.CachedSession(
} }
) )
@dataclass
class GameInfo: class GameInfo:
def __init__(self, game_id: int, num_players: int, variant_id: int, seed: str, score: int, num_turns: int, game_id: int
user_ids: List[int], normalized_usernames: List[str]): num_players: int
self.game_id = game_id variant_id: int
self.num_players = num_players seed: str
self.variant_id = variant_id score: int
self.seed = seed num_turns: int
self.score = score user_ids: List[int]
self.num_turns = num_turns normalized_usernames: List[str]
self.user_ids = user_ids datetime_started: str
self.normalized_usernames = normalized_usernames datetime_ended: str
def fetch_games_for_player(username: str, latest_game_id: int): def fetch_games_for_player(username: str, latest_game_id: int):
@ -64,6 +66,7 @@ def process_game_entry(game_json: Dict, username_dict: Dict, variant_ids: List[i
score = game_json["score"] score = game_json["score"]
num_turns = game_json["numTurns"] num_turns = game_json["numTurns"]
start_time = game_json["datetimeStarted"] start_time = game_json["datetimeStarted"]
end_time = game_json["datetimeFinished"]
game_options = game_json["options"] game_options = game_json["options"]
var_id = game_options["variantID"] var_id = game_options["variantID"]
@ -93,7 +96,7 @@ def process_game_entry(game_json: Dict, username_dict: Dict, variant_ids: List[i
return return
user_ids.append(user_id) user_ids.append(user_id)
return GameInfo(game_id, num_players, var_id, seed, score, num_turns, user_ids, normalized_usernames) return GameInfo(game_id, num_players, var_id, seed, score, num_turns, user_ids, normalized_usernames, start_time, end_time)
def fetch_games_for_all_players(): def fetch_games_for_all_players():
@ -145,7 +148,7 @@ def store_new_games(games: Dict[int, GameInfo]):
# Now, iterate over all games and convert to tuples for insertion # Now, iterate over all games and convert to tuples for insertion
for game in sorted(games.values(), key=lambda game_info: game_info.game_id): for game in sorted(games.values(), key=lambda game_info: game_info.game_id):
tup = (game.game_id, game.num_players, game.variant_id, game.seed, game.score, game.num_turns) tup = (game.game_id, game.num_players, game.variant_id, game.seed, game.score, game.num_turns, game.datetime_started, game.datetime_ended)
games_vals.append(tup) games_vals.append(tup)
for player_id in game.user_ids: for player_id in game.user_ids:
tup = (game.game_id, player_id) tup = (game.game_id, player_id)
@ -160,7 +163,7 @@ def store_new_games(games: Dict[int, GameInfo]):
# (for example, because we forced a download refresh) # (for example, because we forced a download refresh)
psycopg2.extras.execute_values( psycopg2.extras.execute_values(
cur, cur,
"INSERT INTO games (id, num_players, variant_id, seed, score, num_turns) " "INSERT INTO games (id, num_players, variant_id, seed, score, num_turns, datetime_started, datetime_finished) "
"VALUES %s " "VALUES %s "
"ON CONFLICT (id) DO NOTHING", "ON CONFLICT (id) DO NOTHING",
games_vals games_vals
@ -190,6 +193,7 @@ def store_new_games(games: Dict[int, GameInfo]):
def detailed_fetch_game(game_id: int) -> bool: def detailed_fetch_game(game_id: int) -> bool:
""" """
Fetches full game details from the server and stores it in local DB if this game is a league game. Fetches full game details from the server and stores it in local DB if this game is a league game.
@warning: Game data has to be present in database already, game details will then be added.
@param game_id: Game ID from hanab.live @param game_id: Game ID from hanab.live
@return: Whether the processed game was accepted as a league game, i.e. inserted into the DB @return: Whether the processed game was accepted as a league game, i.e. inserted into the DB
""" """
@ -242,14 +246,8 @@ def detailed_fetch_game(game_id: int) -> bool:
conn = conn_manager.get_connection() conn = conn_manager.get_connection()
cur = conn_manager.get_new_cursor() cur = conn_manager.get_new_cursor()
# Insert the game into the database if not present # Note that we assume the game is present in the database already
cur.execute("INSERT INTO games "
"(id, num_players, variant_id, seed, score, num_turns) "
"VALUES "
"(%s, %s, %s, %s, %s, %s) "
"ON CONFLICT (id) DO NOTHING",
(game_id, num_players, var_id, seed, game.score, len(actions)))
# TODO Max: Check if len(actions) is the correct number here, in case there was a VTK action, we might want to subtract one
game_participants_vals = [] game_participants_vals = []
for seat, user_id in enumerate(user_ids): for seat, user_id in enumerate(user_ids):

View file

@ -7,6 +7,9 @@ import urllib.parse
import jinja2 import jinja2
import datetime import datetime
import psycopg2.extras import psycopg2.extras
import requests_cache
import platformdirs
import stats import stats
import constants import constants
@ -17,6 +20,7 @@ from dataclasses import dataclass
from database import conn_manager from database import conn_manager
@dataclass @dataclass
class PlayerEntry: class PlayerEntry:
player_name: str player_name: str
@ -106,6 +110,7 @@ class GameRow:
seed: str seed: str
score: int score: int
num_turns: int num_turns: int
datetime_finished: datetime.datetime
league_id: int league_id: int
num_bdrs: int num_bdrs: int
num_crits_lost: int num_crits_lost: int
@ -169,6 +174,7 @@ def get_games() -> List[GameRow]:
" seed," " seed,"
" score," " score,"
" num_turns," " num_turns,"
" datetime_finished,"
" game_data.league_id," " game_data.league_id,"
" num_bottom_deck_risks AS num_bdrs," " num_bottom_deck_risks AS num_bdrs,"
" num_crits_lost," " num_crits_lost,"
@ -188,6 +194,7 @@ def get_games() -> List[GameRow]:
" seed," " seed,"
" score," " score,"
" num_turns," " num_turns,"
" datetime_finished,"
" games.league_id," " games.league_id,"
" num_bottom_deck_risks," " num_bottom_deck_risks,"
" num_crits_lost " " num_crits_lost "
@ -213,7 +220,7 @@ def get_games() -> List[GameRow]:
" ON variant_ratings.league_id = game_data.league_id AND variant_ratings.primary_change = true " " ON variant_ratings.league_id = game_data.league_id AND variant_ratings.primary_change = true "
"GROUP BY (" "GROUP BY ("
" game_data.game_id, game_data.num_players, users, user_ids, game_data.variant_id, variants.name, seed, score, num_turns," " game_data.game_id, game_data.num_players, users, user_ids, game_data.variant_id, variants.name, seed, score, num_turns,"
" game_data.league_id, num_bottom_deck_risks, num_crits_lost, change, value_after, rating_type, user_rating_changes, user_ratings_after" " game_data.league_id, num_bottom_deck_risks, num_crits_lost, change, value_after, rating_type, user_rating_changes, user_ratings_after, datetime_finished"
" ) " " ) "
"ORDER BY league_id DESC", "ORDER BY league_id DESC",
(True,) (True,)
@ -221,11 +228,13 @@ def get_games() -> List[GameRow]:
res = [] res = []
for row in cur.fetchall(): for row in cur.fetchall():
row['game_outcomes'] = [stats.GameOutcome(outcome).string for outcome in row['game_outcomes']] row['game_outcomes'] = [stats.GameOutcome(outcome).string for outcome in row['game_outcomes']]
if row['variant_rating_change'] is not None: game_row = GameRow(**row)
row['variant_rating_change'] = round(row['variant_rating_change'], 2) if game_row.variant_rating_change is not None:
if row['variant_rating_after'] is not None: game_row.variant_rating_change = round(game_row.variant_rating_change, 2)
row['variant_rating_after'] = round(row['variant_rating_after'], 2) if game_row.variant_rating_after is not None:
res.append(GameRow(**row)) game_row.variant_rating_after = round(game_row.variant_rating_after, 2)
game_row.datetime_finished = game_row.datetime_finished.astimezone(datetime.timezone.utc).isoformat()
res.append(game_row)
return res return res
@ -584,6 +593,18 @@ def render_main_site(env: jinja2.Environment, out_dir: Path):
shutil.copytree('deps/tabulator/dist/css', 'build/css', dirs_exist_ok=True) shutil.copytree('deps/tabulator/dist/css', 'build/css', dirs_exist_ok=True)
shutil.copytree('deps/tabulator/dist/js', 'build/js', dirs_exist_ok=True) shutil.copytree('deps/tabulator/dist/js', 'build/js', dirs_exist_ok=True)
session = requests_cache.CachedSession(
platformdirs.user_cache_dir(constants.APP_NAME) + '/luxon.cache',
urls_expire_after={
'*': requests_cache.NEVER_EXPIRE,
}
)
response = session.get("https://moment.github.io/luxon/global/luxon.min.js")
if not response.status_code == 200:
raise ValueError("Failed to get luxon library.")
with open('build/js/luxon.min.js', 'w') as file:
file.write(response.text)
def render_variant_pages(env: jinja2.Environment, out_dir: Path): def render_variant_pages(env: jinja2.Environment, out_dir: Path):
variant_template = env.get_template('variant.html') variant_template = env.get_template('variant.html')
@ -617,6 +638,7 @@ def render_player_pages(env: jinja2.Environment, out_dir: Path):
games = get_games() games = get_games()
games_grouped_by_player = group_games_by_player(games) games_grouped_by_player = group_games_by_player(games)
print(games_grouped_by_player)
player_template = env.get_template('player.html') player_template = env.get_template('player.html')
for player_name, player_stat in player_stats.items(): for player_name, player_stat in player_stats.items():

View file

@ -17,6 +17,8 @@
<!-- Tabulator JS dependency --> <!-- Tabulator JS dependency -->
<script type="text/javascript" src="/js/tabulator.min.js"></script> <script type="text/javascript" src="/js/tabulator.min.js"></script>
<!-- Luxon JS dependency. Needed for some tabulator formatters. -->
<script type="text/javascript" src="/js/luxon.min.js"></script>
{% block navbar %}{% endblock %} {% block navbar %}{% endblock %}
{% block content %}{% endblock %} {% block content %}{% endblock %}

View file

@ -75,6 +75,11 @@ var table_{{div_id}} = new Tabulator("#table-{{div_id}}", {
urlPrefix: "https://hanab.live/replay/", urlPrefix: "https://hanab.live/replay/",
target:"_blank" target:"_blank"
}}, }},
{title: "Played", field: "datetime_finished", formatter: "datetime", formatterParams:{
inputFormat: "iso",
timezone: "system",
outputFormat: "dd/MM HH:mm"
}},
{% if show_player_num %} {% if show_player_num %}
{title: "# Players", field: "num_players"}, {title: "# Players", field: "num_players"},
{% endif %} {% endif %}