From a0bdf9e37e515ffdc987f7ab611c2e91b88fa0c0 Mon Sep 17 00:00:00 2001 From: John Ahlroos Date: Tue, 21 Apr 2026 12:59:55 +0200 Subject: [PATCH] Support all time resolutions on all counter views --- .streamlit/config.toml | 2 +- Dockerfile | 2 +- app/pages/counters.py | 20 +-- app/pages/stats.py | 81 +++++++++--- app/queries/connection.py | 9 ++ app/queries/crud.py | 64 ++++++++++ app/queries/daily_stats.py | 66 ++++++++++ app/queries/monthly_stats.py | 79 ++++++++++++ app/queries/weekly_stats.py | 73 +++++++++++ app/queries/yearly_stats.py | 73 +++++++++++ app/sql.py | 230 ----------------------------------- app/styles.py | 6 +- 12 files changed, 442 insertions(+), 263 deletions(-) create mode 100644 app/queries/connection.py create mode 100644 app/queries/crud.py create mode 100644 app/queries/daily_stats.py create mode 100644 app/queries/monthly_stats.py create mode 100644 app/queries/weekly_stats.py create mode 100644 app/queries/yearly_stats.py delete mode 100644 app/sql.py diff --git a/.streamlit/config.toml b/.streamlit/config.toml index bd68ea6..dfd04be 100644 --- a/.streamlit/config.toml +++ b/.streamlit/config.toml @@ -1,7 +1,7 @@ [server] port = 8501 address = "0.0.0.0" -enableStaticServing = true +enableStaticServing = false [browser] gatherUsageStats = false diff --git a/Dockerfile b/Dockerfile index 343d393..e695a2b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ VOLUME /app/data RUN touch .streamlit/secrets.toml \ && toml add_section --toml-path='.streamlit/secrets.toml' 'connections.sqlite' \ - && toml set --toml-path='.streamlit/secrets.toml' 'connections.sqlite.type' 'sql' \ + && toml set --toml-path='.streamlit/secrets.toml' 'connections.sqlite.type' 'queries' \ && toml set --toml-path='.streamlit/secrets.toml' 'connections.sqlite.url' 'sqlite:///data/daily-counter.db' HEALTHCHECK --interval=60s --retries=5 CMD wget -qO- http://127.0.0.1:8501/_stcore/health || exit 1 diff --git a/app/pages/counters.py b/app/pages/counters.py index a3c2e46..f586936 100644 --- a/app/pages/counters.py +++ b/app/pages/counters.py @@ -1,11 +1,11 @@ import streamlit as st -import sql +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:") def _add_counter(): - colors = sql.get_colors(1) + 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]) @@ -16,7 +16,7 @@ def _add_counter(): 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:"): - sql.create_counter(title, CounterType[counter_type_name], color) + crud.create_counter(title, CounterType[counter_type_name], color) st.rerun() @@ -26,10 +26,10 @@ def _remove_counter(counter_id:int): st.subheader("Are you sure?") with st.container(horizontal=True, width="stretch", horizontal_alignment="center"): if st.form_submit_button("Confirm", icon=":material/delete:"): - sql.remove_counter(counter_id) + crud.remove_counter(counter_id) st.rerun() -df = sql.get_counters() +df = crud.get_counters() with st.container(key="counter-table"): for counter_id, name, counter_type_str, color in zip(df['id'], df['name'], df['type'], df['color']): @@ -38,7 +38,7 @@ with st.container(key="counter-table"): st.header(f":material/calendar_clock: {name}", width="stretch") if st.button("", icon=":material/exposure_plus_1:", key=f"increment_counter_{counter_id}"): - sql.increment_counter(counter_id) + crud.increment_counter(counter_id) st.rerun() if st.button("", icon=":material/delete_forever:", key=f"remove_counter_{counter_id}"): @@ -51,22 +51,22 @@ with st.container(key="counter-table"): stats_prev_unit = counter_type.previous_unit_text() match counter_type: case CounterType.DAILY.value | CounterType.SIMPLE.value: - stats = sql.get_daily_analytics(counter_id) + stats = daily_stats.get_daily_analytics(counter_id) stats_current = stats.iloc[0]["count"] stats_prev = stats.iloc[1]["count"] case CounterType.WEEKLY.value: - stats = sql.get_weekly_analytics(counter_id) + stats = weekly_stats.get_weekly_analytics(counter_id) stats_current = stats.iloc[0]["count"] stats_prev = stats.iloc[1]["count"] case CounterType.MONTHLY.value: - stats = sql.get_monthly_analytics(counter_id) + stats = monthly_stats.get_monthly_analytics(counter_id) stats_current = stats.iloc[-1]["count"] stats_prev = stats.iloc[-2]["count"] case CounterType.YEARLY.value: - stats = sql.get_yearly_analytics(counter_id) + stats = yearly_stats.get_yearly_analytics(counter_id) stats_current = stats.iloc[-1]["count"] stats_prev = stats.iloc[-2]["count"] diff --git a/app/pages/stats.py b/app/pages/stats.py index 3f9d889..19ff1ce 100644 --- a/app/pages/stats.py +++ b/app/pages/stats.py @@ -1,44 +1,89 @@ +import enum import logging import streamlit as st import json -import sql import pandas as pd from enums import CounterType +from enum import StrEnum +from queries import crud, daily_stats, weekly_stats, monthly_stats, yearly_stats logger = logging.getLogger(__name__) +counter_type_names = ([e.name for e in CounterType]) +options = counter_type_names +options.remove(CounterType.SIMPLE.name) + if "counter_id" in st.query_params.keys(): + + ''' + Show specific Counter analytics where the counter id is passed as query parameter "counter_id". + ''' + counter_id = int(st.query_params["counter_id"]) - df = sql.get_counter(counter_id) + df = crud.get_counter(counter_id) - st.header('Counter: ' + df['name']) + counter_type_id = df['type'] - 1 + counter_type = [e for e in CounterType][counter_type_id] + counter_color ='#' + df['color'] + + with st.container(horizontal_alignment="right", vertical_alignment="bottom", horizontal=True): + st.header('Counter: ' + df['name']) + selection = st.segmented_control("Time Range", options, selection_mode="single", required=True, default=counter_type.name, label_visibility="hidden") + + match getattr(CounterType, selection): + case CounterType.DAILY: + st.bar_chart(daily_stats.get_daily_analytics(counter_id), x="date", y="count", color=counter_color) + case CounterType.WEEKLY: + st.bar_chart(weekly_stats.get_weekly_analytics(counter_id), x="week", y="count", color=counter_color) + case CounterType.MONTHLY: + st.bar_chart(monthly_stats.get_monthly_analytics(counter_id), x="month", y="count", color=counter_color) + case CounterType.YEARLY: + st.bar_chart(yearly_stats.get_yearly_analytics(counter_id), x="year", y="count", color=counter_color) + case _: + logger.error(f"Unknown selection: {selection}") - color ='#' + df['color'] - match df['type']: - case CounterType.DAILY.value | CounterType.SIMPLE.value: - st.bar_chart(sql.get_daily_analytics(int(df['id'])), x="date", y="count", color=color) - case CounterType.WEEKLY.value: - st.bar_chart(sql.get_weekly_analytics(int(df['id'])), x="week", y="count", color=color) - case CounterType.MONTHLY.value: - st.bar_chart(sql.get_monthly_analytics(int(df['id'])), x="month", y="count", color=color) - case CounterType.YEARLY.value: - st.bar_chart(sql.get_yearly_analytics(int(df['id'])), x="year", y="count", color=color) else: - st.header("Statistics") + ''' + By default, if no counter id is passed then show all counters in a a stacked graph + ''' + with st.container(horizontal_alignment="right", vertical_alignment="bottom", horizontal=True): + st.header("Statistics") + selection = st.segmented_control("Time range", options, selection_mode="single", default=f"{CounterType.DAILY.name}", required=True, label_visibility="hidden") + + selectedRange = getattr(CounterType, selection) + match getattr(CounterType, selection): + case CounterType.DAILY: + unit = 'date' + unit_label = 'Date' + entries = daily_stats.get_all_daily_analytics() + case CounterType.WEEKLY: + unit = 'week' + unit_label ='Week' + entries = weekly_stats.get_all_weekly_analytics() + case CounterType.MONTHLY: + unit = 'month' + unit_label = 'Month' + entries = monthly_stats.get_all_monthly_analytics() + case CounterType.YEARLY: + unit = 'year' + unit_label = 'Year' + entries = yearly_stats.get_all_yearly_analytics() + case _: + logger.error(f"Unknown selection: {selection}") - entries = sql.get_analytics() entries_norm = pd.json_normalize(entries.counters.apply(json.loads)).fillna(0) entries_full = pd.concat([entries, entries_norm], axis=1).drop(['counters'], axis=1) - selected_counters = [c for c in entries_full.columns if c != "date"] - all_counters = sql.get_counters() + selected_counters = [c for c in entries_full.columns if c != unit] + all_counters = crud.get_counters() + colors = all_counters.loc[all_counters['name'].isin(selected_counters), ["name", "color"]] colors.name = colors.name.astype("category") colors.name = colors.name.cat.set_categories(selected_counters) colors = colors.sort_values(["name"]) colors = colors.color.apply(lambda c: "#" + c).tolist() - st.bar_chart(entries_full, x="date", x_label="Date", y_label="Count", color=colors) + st.bar_chart(entries_full, x=unit, x_label=unit_label, y_label="Count", color=colors) diff --git a/app/queries/connection.py b/app/queries/connection.py new file mode 100644 index 0000000..2a2fe55 --- /dev/null +++ b/app/queries/connection.py @@ -0,0 +1,9 @@ + +import streamlit as st +from sqlalchemy.sql import text +from streamlit.connections import BaseConnection + +connection: BaseConnection = st.connection("sqlite") + +with connection.session as configure_session: + configure_session.execute(text('PRAGMA foreign_keys=ON')) \ No newline at end of file diff --git a/app/queries/crud.py b/app/queries/crud.py new file mode 100644 index 0000000..bba17eb --- /dev/null +++ b/app/queries/crud.py @@ -0,0 +1,64 @@ +import logging +import streamlit as st +from sqlalchemy.sql import text +from queries.connection import connection +from enums import CounterType + +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: + try: + query = text('INSERT INTO counters (name, type, color) VALUES (:title, :type, :color)') + session.execute(query, {'title': title, 'type': counter_type, 'color': counter_color}) + session.commit() + except Exception as e: + logger.error(e) + session.rollback() + +def get_counters(): + try: + 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: + try: + query = text('INSERT INTO entries (counter_id) VALUES (:id)') + session.execute(query, {'id': counter_id}) + session.commit() + except Exception as e: + logger.error(e) + session.rollback() + + +def remove_counter(counter_id:int) -> None: + logger.info("Removing counter %s", counter_id) + with connection.session as session: + try: + query = text('DELETE FROM counters WHERE id = :id') + session.execute(query, {'id': counter_id}) + session.commit() + except Exception as e: + 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] + except Exception as e: + logger.error(e) + return None + + +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}) + except Exception as e: + logger.error(e) + return None diff --git a/app/queries/daily_stats.py b/app/queries/daily_stats.py new file mode 100644 index 0000000..948ff9d --- /dev/null +++ b/app/queries/daily_stats.py @@ -0,0 +1,66 @@ +import logging +from queries.connection import connection + +logger = logging.getLogger(__name__) + +def get_all_daily_analytics(end_date:str = 'now'): + try: + return connection.query(''' + WITH RECURSIVE timeseries(d) AS ( + VALUES(date(:end_date)) + UNION ALL + SELECT date(d, '-1 day') as d + FROM timeseries + WHERE d > date(:end_date, '-30 days') + ), + stats AS ( + SELECT + date(timestamp) as d, + counter_id, + sum(increment) as count + FROM entries + group by counter_id, date(timestamp) + ) + select + s.d as date, + case + when counter_id is null then json_object() + else json_group_object(name, count) + end as counters + FROM timeseries s + left outer join stats t on s.d = t.d + left join counters c on t.counter_id = c.id + GROUP by s.d + ''', params={"end_date": end_date}, ttl=0) + except Exception as e: + logger.error(e) + return None + + +def get_daily_analytics(counter_id:int, end_date:str = 'now'): + try: + return connection.query(''' + WITH RECURSIVE timeseries(d) AS ( + VALUES(date(:end_date)) + UNION ALL + SELECT date(d, '-1 day') as d + FROM timeseries + WHERE d > date(:end_date, '-7 days') + ), + stats AS ( + SELECT + date(timestamp) as d, + sum(increment) as count + FROM entries + where counter_id = :id + group by date(timestamp) + ) + SELECT + t.d as "date", + coalesce(s.count, 0) as count + FROM timeseries as t + LEFT JOIN stats as s on s.d = t.d + ''', params={'id': counter_id, "end_date": end_date}, ttl=0) + except Exception as e: + logger.error(e) + return None \ No newline at end of file diff --git a/app/queries/monthly_stats.py b/app/queries/monthly_stats.py new file mode 100644 index 0000000..ff8ddcf --- /dev/null +++ b/app/queries/monthly_stats.py @@ -0,0 +1,79 @@ +import logging +from queries.connection import connection + +logger = logging.getLogger(__name__) + +def get_all_monthly_analytics(end_date:str = 'now'): + try: + return connection.query(''' + WITH RECURSIVE timeseries(d) AS ( + VALUES(date(:end_date,'start of year')) + UNION ALL + SELECT date(d, '+1 month') as d + FROM timeseries + WHERE d < date(:end_date, '-1 month') + ), + months AS ( + SELECT + strftime('%m',d) as m, + strftime('%Y',d) as y + FROM timeseries + ), + stats AS ( + SELECT + strftime('%m', timestamp) as m, + strftime('%Y', timestamp) as y, + counter_id, + sum(increment) as count + FROM entries + group by counter_id, strftime('%m', timestamp), strftime('%Y', timestamp) + ) + select + concat(m.m,', ',m.y) as "month", + case + when counter_id is null then json_object() + else json_group_object(name, count) + end as counters + FROM months m + left outer join stats t on m.m = t.m and m.y = t.y + left join counters c on t.counter_id = c.id + GROUP by m.m, m.y + ''', params={"end_date": end_date}, ttl=0) + except Exception as e: + logger.error(e) + return None + +def get_monthly_analytics(counter_id:int, end_date:str = 'now'): + try: + return connection.query(''' + WITH RECURSIVE timeseries(d) AS ( + VALUES( date(:end_date, 'start of year')) + UNION ALL + SELECT date(d, '+1 month') + FROM timeseries + WHERE d < date(:end_date, '-1 month') + ), + months AS ( + SELECT + strftime('%m',d) as m, + strftime('%Y',d) as y + FROM timeseries + ), + stats AS ( + SELECT + strftime('%m', timestamp) as m, + strftime('%Y', timestamp) as y, + sum(increment) as count + FROM entries + where counter_id = :id + group by strftime('%m', timestamp), strftime('%Y', timestamp) + ) + SELECT + concat(m.m,', ',m.y) as "month", + coalesce(s.count, 0) as count + FROM months as m + LEFT JOIN stats as s on s.m = m.m and s.y = m.y + ''', params={'id': counter_id, "end_date": end_date}, ttl=0) + except Exception as e: + logger.error(e) + return None \ No newline at end of file diff --git a/app/queries/weekly_stats.py b/app/queries/weekly_stats.py new file mode 100644 index 0000000..e97587b --- /dev/null +++ b/app/queries/weekly_stats.py @@ -0,0 +1,73 @@ +import logging +from queries.connection import connection + +logger = logging.getLogger(__name__) + +def get_all_weekly_analytics(end_date:str = 'now'): + try: + return connection.query(''' + WITH RECURSIVE timeseries(d) AS ( + VALUES(date(:end_date, 'weekday 0')) + UNION ALL + SELECT date(d, '-7 day') as d + FROM timeseries + WHERE d > date(:end_date, '-30 days') + ), + weeks AS ( + SELECT strftime('%W',d) as w + FROM timeseries + ), + stats AS ( + SELECT + strftime('%W', timestamp) as w, + counter_id, + sum(increment) as count + FROM entries + group by counter_id, strftime('%W', timestamp) + ) + select + s.w as week, + case + when counter_id is null then json_object() + else json_group_object(name, count) + end as counters + FROM weeks s + left outer join stats t on s.w = t.w + left join counters c on t.counter_id = c.id + GROUP by s.w + ''', params={"end_date": end_date}, ttl=0) + except Exception as e: + logger.error(e) + return None + +def get_weekly_analytics(counter_id:int, end_date:str = 'now'): + try: + return connection.query(''' + WITH RECURSIVE timeseries(d) AS ( + VALUES(date(:end_date, 'weekday 0')) + UNION ALL + SELECT date(d, '-7 day') + FROM timeseries + WHERE d > date(:end_date, '-30 days') + ), + weeks AS ( + SELECT strftime('%W',d) as w + FROM timeseries + ), + stats AS ( + SELECT + strftime('%W', timestamp) as w, + sum(increment) as count + FROM entries + where counter_id = :id + group by strftime('%W', timestamp) + ) + SELECT + w.w as "week", + coalesce(s.count, 0) as count + FROM weeks as w + LEFT JOIN stats as s on s.w = w.w + ''', params={'id': counter_id, "end_date": end_date}, ttl=0) + except Exception as e: + logger.error(e) + return None \ No newline at end of file diff --git a/app/queries/yearly_stats.py b/app/queries/yearly_stats.py new file mode 100644 index 0000000..c330445 --- /dev/null +++ b/app/queries/yearly_stats.py @@ -0,0 +1,73 @@ +import logging +from queries.connection import connection + +logger = logging.getLogger(__name__) + +def get_all_yearly_analytics(end_date:str = 'now'): + try: + return connection.query(''' + WITH RECURSIVE timeseries(d) AS ( + VALUES(date(:end_date,'start of year', '-4 years')) + UNION ALL + SELECT date(d, '+1 year') as d + FROM timeseries + WHERE d < date(:end_date, '-1 year') + ), + years AS ( + SELECT strftime('%Y',d) as y + FROM timeseries + ), + stats AS ( + SELECT + strftime('%Y', timestamp) as y, + counter_id, + sum(increment) as count + FROM entries + group by counter_id, strftime('%Y', timestamp) + ) + select + y.y as "year", + case + when counter_id is null then json_object() + else json_group_object(name, count) + end as counters + FROM years y + left outer join stats t on y.y = t.y + left join counters c on t.counter_id = c.id + GROUP by y.y + ''', params={"end_date": end_date}, ttl=0) + except Exception as e: + logger.error(e) + return None + +def get_yearly_analytics(counter_id:int, end_date:str = 'now'): + try: + return connection.query(''' + WITH RECURSIVE timeseries(d) AS ( + VALUES( date(:end_date, 'start of year', '-4 years')) + UNION ALL + SELECT date(d, '+1 year') + FROM timeseries + WHERE d < date(:end_date, '-1 year') + ), + years AS ( + SELECT strftime('%Y',d) as y + FROM timeseries + ), + stats AS ( + SELECT + strftime('%Y', timestamp) as y, + sum(increment) as count + FROM entries + where counter_id = :id + group by strftime('%Y', timestamp) + ) + SELECT + m.y as "year", + coalesce(s.count, 0) as count + FROM years as m + LEFT JOIN stats as s on s.y = m.y + ''', params={'id': counter_id, "end_date": end_date}, ttl=0) + except Exception as e: + logger.error(e) + return None \ No newline at end of file diff --git a/app/sql.py b/app/sql.py deleted file mode 100644 index ea9d2d5..0000000 --- a/app/sql.py +++ /dev/null @@ -1,230 +0,0 @@ -import logging - -import streamlit as st - -from sqlalchemy.sql import text -from enums import CounterType - -logger = logging.getLogger(__name__) - -connection = st.connection("sqlite") -with connection.session as configure_session: - configure_session.execute(text('PRAGMA foreign_keys=ON')) - -def create_counter(title:str, counter_type:CounterType, counter_color) -> None: - logger.info("Adding counter %s", counter_type) - 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}) - session.commit() - except Exception as e: - logger.error(e) - session.rollback() - -def get_counters(): - try: - 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: - try: - query = text('INSERT INTO entries (counter_id) VALUES (:id)') - session.execute(query, {'id': counter_id}) - session.commit() - except Exception as e: - logger.error(e) - session.rollback() - - -def remove_counter(counter_id:int) -> None: - logger.info("Removing counter %s", counter_id) - with connection.session as session: - try: - query = text('DELETE FROM counters WHERE id = :id') - session.execute(query, {'id': counter_id}) - session.commit() - except Exception as e: - 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] - except Exception as e: - logger.error(e) - return None - - -def get_analytics(end_date:str = 'now'): - try: - return connection.query(''' - WITH RECURSIVE timeseries(d) AS ( - VALUES(date(:end_date)) - UNION ALL - SELECT date(d, '-1 day') as d - FROM timeseries - WHERE d > date(:end_date, '-30 days') - ), - stats AS ( - SELECT - date(timestamp) as d, - counter_id, - sum(increment) as count - FROM entries - group by counter_id, date(timestamp) - ) - select - s.d as date, - case - when counter_id is null then json_object() - else json_group_object(name, count) - end as counters - FROM timeseries s - left outer join stats t on s.d = t.d - left join counters c on t.counter_id = c.id - GROUP by s.d - ''', params={"end_date": end_date}, ttl=0) - except Exception as e: - logger.error(e) - return None - -def get_daily_analytics(counter_id:int, end_date:str = 'now'): - try: - return connection.query(''' - WITH RECURSIVE timeseries(d) AS ( - VALUES(date(:end_date)) - UNION ALL - SELECT date(d, '-1 day') as d - FROM timeseries - WHERE d > date(:end_date, '-7 days') - ), - stats AS ( - SELECT - date(timestamp) as d, - sum(increment) as count - FROM entries - where counter_id = :id - group by date(timestamp) - ) - SELECT - t.d as "date", - coalesce(s.count, 0) as count - FROM timeseries as t - LEFT JOIN stats as s on s.d = t.d - ''', params={'id': counter_id, "end_date": end_date}, ttl=0) - except Exception as e: - logger.error(e) - return None - - -def get_weekly_analytics(counter_id:int, end_date:str = 'now'): - try: - return connection.query(''' - WITH RECURSIVE timeseries(d) AS ( - VALUES(date(:end_date, 'weekday 0')) - UNION ALL - SELECT date(d, '-7 day') - FROM timeseries - WHERE d > date(:end_date, '-30 days') - ), - weeks AS ( - SELECT strftime('%W',d) as w - FROM timeseries - ), - stats AS ( - SELECT - strftime('%W', timestamp) as w, - sum(increment) as count - FROM entries - where counter_id = :id - group by strftime('%W', timestamp) - ) - SELECT - w.w as "week", - coalesce(s.count, 0) as count - FROM weeks as w - LEFT JOIN stats as s on s.w = w.w - ''', params={'id': counter_id, "end_date": end_date}, ttl=0) - except Exception as e: - logger.error(e) - return None - -def get_monthly_analytics(counter_id:int, end_date:str = 'now'): - try: - return connection.query(''' - WITH RECURSIVE timeseries(d) AS ( - VALUES( date(:end_date, 'start of year')) - UNION ALL - SELECT date(d, '+1 month') - FROM timeseries - WHERE d < date(:end_date, '-1 month') - ), - months AS ( - SELECT - strftime('%m',d) as m, - strftime('%Y',d) as y - FROM timeseries - ), - stats AS ( - SELECT - strftime('%m', timestamp) as m, - strftime('%Y', timestamp) as y, - sum(increment) as count - FROM entries - where counter_id = :id - group by strftime('%m', timestamp), strftime('%Y', timestamp) - ) - SELECT - concat(m.m,', ',m.y) as "month", - coalesce(s.count, 0) as count - FROM months as m - LEFT JOIN stats as s on s.m = m.m and s.y = m.y - ''', params={'id': counter_id, "end_date": end_date}, ttl=0) - except Exception as e: - logger.error(e) - return None - -def get_yearly_analytics(counter_id:int, end_date:str = 'now'): - try: - return connection.query(''' - WITH RECURSIVE timeseries(d) AS ( - VALUES( date(:end_date, 'start of year', '-4 years')) - UNION ALL - SELECT date(d, '+1 year') - FROM timeseries - WHERE d < date(:end_date, '-1 year') - ), - years AS ( - SELECT strftime('%Y',d) as y - FROM timeseries - ), - stats AS ( - SELECT - strftime('%Y', timestamp) as y, - sum(increment) as count - FROM entries - where counter_id = :id - group by strftime('%Y', timestamp) - ) - SELECT - m.y as "year", - coalesce(s.count, 0) as count - FROM years as m - LEFT JOIN stats as s on s.y = m.y - ''', params={'id': counter_id, "end_date": end_date}, ttl=0) - except Exception as e: - logger.error(e) - return None - -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}) - except Exception as e: - logger.error(e) - return None diff --git a/app/styles.py b/app/styles.py index e497a18..5f7b34e 100644 --- a/app/styles.py +++ b/app/styles.py @@ -1,6 +1,6 @@ import streamlit as st -import sql - +import queries +from queries import crud def _load_css(filepath): with open(filepath) as file: @@ -8,7 +8,7 @@ def _load_css(filepath): def _load_color_selector_styles(): - colors = sql.get_colors(1) #FIXME Change to use user profile color palette + colors = crud.get_colors(1) #FIXME Change to use user profile color palette for idx, c in enumerate(colors.keys()): css_color = '#' + colors[c][0] st.html(f"""