Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2e53e49
Make merge operations the default, not overwrite operations
emigre459 Feb 28, 2025
9457e09
Merge branch 'main' into 2-feature-track-widget-interactions-by-key-n…
emigre459 Feb 28, 2025
2104611
Update st.button wrapper to track by key if one is explicitly user-de…
emigre459 Feb 28, 2025
62441f4
Start using keys in some of the test files for future key-instead-of-…
emigre459 Mar 1, 2025
e63934b
Revert back key-instead-of-label changes
emigre459 Mar 1, 2025
17e15af
bug: Fix pageview and total time recording issues with session-level …
emigre459 Mar 3, 2025
46cbe69
Flesh out the advanced firebase_test page and tab a bit for future ef…
emigre459 Mar 3, 2025
7111d9e
Fix per tests
emigre459 Mar 3, 2025
479c047
Add ability to track session length by day
emigre459 Mar 3, 2025
6814ca7
Enable session-level data view in minimal.py
emigre459 Mar 3, 2025
bc6915b
Create per-day tracking of widget clicks for checkbox
emigre459 Mar 7, 2025
3518040
Ensure that per-day widgets aren't causing KeyError and start trackin…
emigre459 Mar 8, 2025
13e7986
Update all wrappers with new session + aggregate dict iteration and p…
emigre459 Mar 8, 2025
b5446a2
bug: Assume that per_day widget counts are a list of dict, not just a…
emigre459 Mar 8, 2025
fccf70e
Ensure backwards compatibility with new per_day fields and running SA…
emigre459 Mar 8, 2025
7e0dd9e
Formatting updates for tests
emigre459 Mar 8, 2025
7d13d4d
bug: Fix bad per_day array construction when per_day arrays for other…
emigre459 Mar 10, 2025
b7f56fd
bug: Fix lack of firestore loading bool for session_data
emigre459 Mar 11, 2025
806b86c
Add delete_session_data function to remove a record from firestore (a…
emigre459 Mar 11, 2025
8f84736
Add session_id awareness to some of the test pages
emigre459 Mar 11, 2025
c29042d
Update collection name for example purposes
emigre459 Mar 11, 2025
71b75c6
Formatting fixes for tests
emigre459 Mar 11, 2025
0138dc3
Make firebase_test app page run without modification
emigre459 Apr 9, 2025
154135e
Update version number
emigre459 Apr 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions examples/minimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,15 @@

st.markdown("---")

with streamlit_analytics.track():
st.text_input("Write your name")
st.selectbox("Select your favorite", ["cat", "dog", "flower"])
st.button("Click me")
session_id = st.text_input("Session ID", help="This is the session ID that will be used to track individual session data. If not provided, tracking only occurs at an aggregate level.")

if session_id is not None and session_id == "":
session_id = None

with streamlit_analytics.track(session_id=session_id):
st.text_input("Write your name", key="name")
st.selectbox("Select your favorite", ["cat", "dog", "flower"], key="favorite")
st.button("Click me", key="click_me")
streamlit_analytics.data
streamlit_analytics.session_data

61 changes: 50 additions & 11 deletions examples/pages/firebase_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

import streamlit_analytics2 as streamlit_analytics

COLLECTION_NAME = "streamlit-analytics-secrets-approach"
DOCUMENT_NAME = "counts"
PROJECT_NAME = "ev-monitoring-poc"

session_id = st.text_input("Session ID", help="This is the session ID that will be used to track individual session data. If not provided, tracking only occurs at an aggregate level.")

if session_id is not None and session_id == "":
session_id = None
st.write(f"Session ID: {session_id}")
st.write(f"{streamlit_analytics.session_data["loaded_from_firestore"]=}")

tab1, tab2 = st.tabs(["Classic", "Advanced"])


Expand All @@ -13,26 +24,54 @@

with streamlit_analytics.track(
firestore_key_file="pages/firebase-key.json",
firestore_collection_name="streamlit_analytics2",
firestore_document_name="data",
firestore_project_name="streamlit-analtyics2",
firestore_collection_name=COLLECTION_NAME,
firestore_document_name=DOCUMENT_NAME,
firestore_project_name=PROJECT_NAME,
verbose=True,
session_id=session_id,
):

st.text_input("Write something")
st.button("Click me")

st.write("# Aggregate Stats")
st.write(streamlit_analytics.data)
st.write("# Session Stats")
st.write(streamlit_analytics.session_data)

if st.button("Delete session data", key="delete_classic"):
streamlit_analytics.delete_session_data(
session_id,
COLLECTION_NAME,
firestore_project_name=PROJECT_NAME,
firestore_key_file="pages/firebase-key.json"
)


# Advanced storing of key in secrets.toml
with tab2:
st.header("Advanced")
# if st.button("Test Advanced"):

# with streamlit_analytics.track(
# firestore_collection_name="counts",
# streamlit_secrets_firestore_key="firebase",
# firestore_project_name="firestore_project_name",
# verbose=True):
with streamlit_analytics.track(
firestore_collection_name=COLLECTION_NAME,
streamlit_secrets_firestore_key="firebase",
firestore_project_name=PROJECT_NAME,
session_id=session_id,
verbose=False):

st.text_input("Write something", key="text_input2")
st.button("Click me", key="button2")

st.write("# Aggregate Stats")
st.write(streamlit_analytics.data)
st.write("# Session Stats")
st.write(streamlit_analytics.session_data)

# st.text_input("Write something")
# st.button("Click me")
if st.button("Delete session data", key="delete_advanced"):
print("Session deletion button clicked")
streamlit_analytics.delete_session_data(
session_id,
COLLECTION_NAME,
firestore_project_name=PROJECT_NAME,
streamlit_secrets_firestore_key="firebase",
)
11 changes: 8 additions & 3 deletions src/streamlit_analytics2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
Track & visualize user interactions with your streamlit app.
"""

from .main import start_tracking, stop_tracking, track # noqa: F401
from .state import data # noqa: F401
from .main import ( # noqa: F401
delete_session_data,
start_tracking,
stop_tracking,
track,
)
from .state import data, session_data # noqa: F401

from .state import data as counts # noqa: F401 # isort:skip

__version__ = "0.10.4"
__version__ = "0.11.0"
__name__ = "streamlit_analytics2"
2 changes: 1 addition & 1 deletion src/streamlit_analytics2/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import streamlit as st

from . import utils
from .state import data # noqa: F401
from .state import data, session_data # noqa: F401


def show_results(data, reset_callback, unsafe_password=None): # noqa: F811
Expand Down
64 changes: 45 additions & 19 deletions src/streamlit_analytics2/firestore.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import json
from pathlib import Path
from typing import Optional, Union

import streamlit as st
from google.cloud import firestore
from google.oauth2 import service_account
from streamlit import session_state as ss

from .state import data # noqa: F401
from .state import data, session_data # noqa: F401


def sanitize_data(data): # noqa: F811
Expand All @@ -23,12 +24,12 @@ def sanitize_data(data): # noqa: F811

def load(
data, # noqa: F811
service_account_json,
collection_name,
document_name,
streamlit_secrets_firestore_key,
firestore_project_name,
session_id=None,
service_account_json: Optional[Union[str, Path]] = None,
collection_name: Optional[str] = None,
document_name: Optional[str] = "counts",
streamlit_secrets_firestore_key: Optional[str] = None,
firestore_project_name: Optional[str] = None,
session_id: Optional[str] = None,
):
"""Load count data from firestore into `data`."""
firestore_data = None
Expand Down Expand Up @@ -59,21 +60,21 @@ def load(

if firestore_session_data is not None:
for key in firestore_session_data:
if key in ss.session_data:
ss.session_data[key] = firestore_session_data[key]
if key in session_data:
session_data[key] = firestore_session_data[key]

# Log loaded data for debugging
# logging.debug("Data loaded from Firestore: %s", firestore_data)


def save(
data, # noqa: F811
service_account_json,
collection_name,
document_name,
streamlit_secrets_firestore_key,
firestore_project_name,
session_id=None,
service_account_json: Optional[Union[str, Path]] = None,
collection_name: Optional[str] = None,
document_name: Optional[str] = "counts",
streamlit_secrets_firestore_key: Optional[str] = None,
firestore_project_name: Optional[str] = None,
session_id: Optional[str] = None,
):
"""Save count data from `data` to firestore."""

Expand All @@ -94,7 +95,32 @@ def save(

# Attempt to save to Firestore
# creates if doesn't exist
col.document(document_name).set(sanitized_data)
col.document(document_name).set(sanitized_data, merge=True)
if session_id is not None:
sanitized_session_data = sanitize_data(ss.session_data)
col.document(session_id).set(sanitized_session_data)
sanitized_session_data = sanitize_data(session_data)
col.document(session_id).set(sanitized_session_data, merge=True)


def delete(
document_name: str, # noqa: F811
collection_name: str,
service_account_json: Optional[Union[str, Path]] = None,
streamlit_secrets_firestore_key: Optional[str] = None,
firestore_project_name: Optional[str] = None,
):
"""Delete a document from firestore. Commonly used to delete session data when requested by a user by passing session_id as document_name."""
if streamlit_secrets_firestore_key is not None:
print("Using secrets to connect to firestore for deletion")
# Following along here https://blog.streamlit.io/streamlit-firestore-continued/#part-4-securely-deploying-on-streamlit-sharing # noqa: E501
# for deploying to Streamlit Cloud with Firestore
key_dict = json.loads(st.secrets[streamlit_secrets_firestore_key])
creds = service_account.Credentials.from_service_account_info(key_dict)
db = firestore.Client(credentials=creds, project=firestore_project_name)
else:
db = firestore.Client.from_service_account_json(service_account_json)
col = db.collection(collection_name)
# TODO pass user set argument via config screen for the name of document
# currently hard coded to be "counts"

# Delete from firestore
col.document(document_name).delete()
Loading