Add tests and fix issues

This commit is contained in:
2026-04-25 10:38:21 +02:00
parent a0bdf9e37e
commit d84a0eed3f
18 changed files with 911 additions and 567 deletions

View File

@@ -1,32 +1,35 @@
import streamlit as st
from streamlit import dialog
from queries import crud, daily_stats, weekly_stats, monthly_stats, yearly_stats
from enums import CounterType
@st.dialog("Add New Counter", icon=":material/add_box:")
@dialog("Add New Counter", icon=":material/add_box:")
def _add_counter():
colors = crud.get_colors(1)
with st.form(key="add_counter", border=False, clear_on_submit=True):
title = st.text_input("Title:")
counter_type_name = st.selectbox("Type", options=[e.name for e in CounterType])
color = st.radio("Color",
key="color-selector",
title = st.text_input("Title:", key="new_counter_title")
counter_type_name = st.selectbox("Type", options=[e.name for e in CounterType], key="new_counter_type")
selected_color = st.radio("Color",
key="new_counter_color_selector",
width="stretch",
options=[colors[key][0] for key in colors],
format_func=lambda c: f"#{c}")
with st.container(horizontal=True, width="stretch", horizontal_alignment="center"):
if st.form_submit_button(label="Create", icon=":material/save:"):
crud.create_counter(title, CounterType[counter_type_name], color)
if st.form_submit_button(label="Create", icon=":material/save:", key="create_counter_submit_btn"):
if not title:
raise ValueError("Title cannot be empty")
crud.create_counter(title, CounterType[counter_type_name], selected_color)
st.rerun()
@st.dialog("Remove Counter", icon=":material/delete:")
def _remove_counter(counter_id:int):
@dialog("Remove Counter", icon=":material/delete:")
def _remove_counter(remove_counter_id:int):
with st.form(key="remove_counter", border=False, clear_on_submit=True):
st.subheader("Are you sure?")
with st.container(horizontal=True, width="stretch", horizontal_alignment="center"):
if st.form_submit_button("Confirm", icon=":material/delete:"):
crud.remove_counter(counter_id)
if st.form_submit_button("Confirm", icon=":material/delete:", key="remove_counter_submit_btn"):
crud.remove_counter(remove_counter_id)
st.rerun()
df = crud.get_counters()
@@ -89,7 +92,7 @@ with st.container(key="counter-table"):
</style>
""")
if st.button("Add Counter", width="stretch", icon=":material/add_box:"):
if st.button("Add Counter", width="stretch", icon=":material/add_box:", key="new_counter_button"):
_add_counter()

View File

@@ -1,9 +1,13 @@
from os import getenv
import streamlit as st
from sqlalchemy.sql import text
from streamlit.connections import BaseConnection
connection: BaseConnection = st.connection("sqlite")
def connection() -> BaseConnection:
_connection = st.connection("sql", url=getenv('DATABASE_URL'))
with _connection.session as configured_session:
configured_session.execute(text('PRAGMA foreign_keys=ON'))
return _connection
with connection.session as configure_session:
configure_session.execute(text('PRAGMA foreign_keys=ON'))

View File

@@ -8,7 +8,7 @@ logger = logging.getLogger(__name__)
def create_counter(title:str, counter_type:CounterType, counter_color) -> None:
logger.info("Adding counter %s", counter_type)
with connection.session as session:
with connection().session as session:
try:
query = text('INSERT INTO counters (name, type, color) VALUES (:title, :type, :color)')
session.execute(query, {'title': title, 'type': counter_type, 'color': counter_color})
@@ -19,14 +19,14 @@ def create_counter(title:str, counter_type:CounterType, counter_color) -> None:
def get_counters():
try:
return connection.query('SELECT id, name, type, color FROM counters', ttl=0)
return connection().query('SELECT id, name, type, color FROM counters', ttl=0)
except Exception as e:
logger.error(e)
return st.dataframe()
def increment_counter(counter_id:int) -> None:
logger.info("Incrementing counter %s", counter_id)
with connection.session as session:
with connection().session as session:
try:
query = text('INSERT INTO entries (counter_id) VALUES (:id)')
session.execute(query, {'id': counter_id})
@@ -35,10 +35,9 @@ def increment_counter(counter_id:int) -> None:
logger.error(e)
session.rollback()
def remove_counter(counter_id:int) -> None:
logger.info("Removing counter %s", counter_id)
with connection.session as session:
with connection().session as session:
try:
query = text('DELETE FROM counters WHERE id = :id')
session.execute(query, {'id': counter_id})
@@ -47,10 +46,9 @@ def remove_counter(counter_id:int) -> None:
logger.error(e)
session.rollback()
def get_counter(counter_id:int):
try:
return connection.query('SELECT * FROM counters WHERE id = :id', params={'id': counter_id}, ttl=0).iloc[0]
return connection().query('SELECT * FROM counters WHERE id = :id', params={'id': counter_id}, ttl=0).iloc[0]
except Exception as e:
logger.error(e)
return None
@@ -58,7 +56,7 @@ def get_counter(counter_id:int):
def get_colors(palette_id:int):
try:
return connection.query('''SELECT color1,color2,color3,color4,color5 FROM color_palettes WHERE id = :id''', params={'id': palette_id})
return connection().query('''SELECT color1,color2,color3,color4,color5 FROM color_palettes WHERE id = :id''', params={'id': palette_id})
except Exception as e:
logger.error(e)
return None

View File

@@ -5,7 +5,7 @@ logger = logging.getLogger(__name__)
def get_all_daily_analytics(end_date:str = 'now'):
try:
return connection.query('''
return connection().query('''
WITH RECURSIVE timeseries(d) AS (
VALUES(date(:end_date))
UNION ALL
@@ -39,7 +39,7 @@ def get_all_daily_analytics(end_date:str = 'now'):
def get_daily_analytics(counter_id:int, end_date:str = 'now'):
try:
return connection.query('''
return connection().query('''
WITH RECURSIVE timeseries(d) AS (
VALUES(date(:end_date))
UNION ALL

View File

@@ -5,7 +5,7 @@ logger = logging.getLogger(__name__)
def get_all_monthly_analytics(end_date:str = 'now'):
try:
return connection.query('''
return connection().query('''
WITH RECURSIVE timeseries(d) AS (
VALUES(date(:end_date,'start of year'))
UNION ALL
@@ -45,7 +45,7 @@ def get_all_monthly_analytics(end_date:str = 'now'):
def get_monthly_analytics(counter_id:int, end_date:str = 'now'):
try:
return connection.query('''
return connection().query('''
WITH RECURSIVE timeseries(d) AS (
VALUES( date(:end_date, 'start of year'))
UNION ALL

View File

@@ -5,7 +5,7 @@ logger = logging.getLogger(__name__)
def get_all_weekly_analytics(end_date:str = 'now'):
try:
return connection.query('''
return connection().query('''
WITH RECURSIVE timeseries(d) AS (
VALUES(date(:end_date, 'weekday 0'))
UNION ALL
@@ -42,7 +42,7 @@ def get_all_weekly_analytics(end_date:str = 'now'):
def get_weekly_analytics(counter_id:int, end_date:str = 'now'):
try:
return connection.query('''
return connection().query('''
WITH RECURSIVE timeseries(d) AS (
VALUES(date(:end_date, 'weekday 0'))
UNION ALL

View File

@@ -5,7 +5,7 @@ logger = logging.getLogger(__name__)
def get_all_yearly_analytics(end_date:str = 'now'):
try:
return connection.query('''
return connection().query('''
WITH RECURSIVE timeseries(d) AS (
VALUES(date(:end_date,'start of year', '-4 years'))
UNION ALL
@@ -42,7 +42,7 @@ def get_all_yearly_analytics(end_date:str = 'now'):
def get_yearly_analytics(counter_id:int, end_date:str = 'now'):
try:
return connection.query('''
return connection().query('''
WITH RECURSIVE timeseries(d) AS (
VALUES( date(:end_date, 'start of year', '-4 years'))
UNION ALL

View File

@@ -1,18 +1,19 @@
import streamlit as st
import logging
from logger import init_logger
from styles import init_styles
init_logger()
init_styles()
if st.user and not st.user.is_logged_in:
with st.container(width="stretch", height="stretch", horizontal_alignment="center"):
st.title("Daily Counter", width="stretch", text_alignment="center")
st.text("Please log in to use this app", width="stretch", text_alignment="center")
st.space()
if st.button("Log in"):
st.login()
if hasattr(st, 'user') and hasattr(st.user, 'is_logged_in'):
if not st.user.is_logged_in:
with st.container(width="stretch", height="stretch", horizontal_alignment="center"):
st.title("Daily Counter", width="stretch", text_alignment="center")
st.text("Please log in to use this app", width="stretch", text_alignment="center")
st.space()
if st.button("Log in"):
st.login()
else:
counters = st.Page("pages/counters.py", title="Counters", icon=":material/update:")
stats = st.Page("pages/stats.py", title="Statistics", icon=":material/chart_data:")

View File

@@ -13,7 +13,7 @@ def _load_color_selector_styles():
css_color = '#' + colors[c][0]
st.html(f"""
<style>
.st-key-color-selector label:has(> input[value='{idx}']) {{
.st-key-new_counter_color_selector label:has(> input[value='{idx}']) {{
background-color: {css_color};
}}
</style>