Giter Site home page Giter Site logo

jrieke / streamlit-analytics Goto Github PK

View Code? Open in Web Editor NEW
244.0 8.0 45.0 1.72 MB

๐Ÿ‘€ Track & visualize user interactions with your streamlit app

License: MIT License

Python 100.00%
streamlit monitoring analytics webapp data-visualization machine-learning google-analytics

streamlit-analytics's Introduction

streamlit-analytics ย ๐Ÿ‘€

PyPi

Track & visualize user interactions with your streamlit app.

This is a small extension for the fantastic streamlit framework. With just one line of code, it counts page views, tracks all widget interactions across users, and visualizes the results directly in your browser. Think Google Analytics but for streamlit.

Alpha version, use with care.


๐ŸŽˆ Live Demo ๐ŸŽˆ


Installation

pip install streamlit-analytics

How to use it

import streamlit as st
import streamlit_analytics

with streamlit_analytics.track():
    st.text_input("Write something")
    st.button("Click me")

That's it! ๐ŸŽˆ All page views and user inputs are now tracked and counted. Of course, you can also use any other streamlit widget in the with block (both from st. and st.sidebar.).

Note: One thing that doesn't work (yet) is tracking widgets created directly from containers, expanders, or columns (e.g. st.expander().button("foo")). Instead, please use a with statement, e.g. with st.expander(): st.button("foo").

To view the results, open your app like normal and append ?analytics=on to the URL (e.g. http://localhost:8501/?analytics=on). The results are then shown directly below your app (see image above).

More options

  • If you don't want a huge with block, you can also do:

    streamlit_analytics.start_tracking()
    # your streamlit code here
    streamlit_analytics.stop_tracking()
  • You can password-protect your analytics results with:

    streamlit_analytics.track(unsafe_password="test123")
    # or pass the same arg to `stop_tracking`

    The app will then ask for this password before showing any results. Do not choose an important password here, it's not encrypted. If you push your code to Github, you should probably store the password in a .env file (which is in .gitignore) and load it via dotenv.

  • If you don't want the results to get reset after restarting streamlit (e.g. during deployment), you can sync them to a Firestore database. Follow this blogpost to set up the database and pass the key file and collection name:

    streamlit_analytics.track(firestore_key_file="firebase-key.json", firestore_collection_name="counts")
    # or pass the same args to `start_tracking` AND `stop_tracking`
  • You can store analytics results as a json file with:

    streamlit_analytics.track(save_to_json="path/to/file.json")
    # or pass the same arg to `stop_tracking`

    And load with:

    streamlit_analytics.track(load_from_json="path/to/file.json")
    # or pass the same arg to `start_tracking`

    (Thanks to @Uranium2 for implementing loading!)

    You can also combine both args to persist data to a json file. Note that this file might get deleted when doing a fresh deploy on a cloud service. Use Firestore instead for persistence, see above. Also note that load_from_json will fail silently if the JSON file does not exist. Writing to JSON may lead to problems with concurrency if many users access the site at the same time.

TODO

PRs are welcome! If you want to work on any of these things, please open an issue to coordinate.

  • Pass all settings args in start_tracking and not in stop_tracking

  • Do not track default values for selectbox, text_input etc. This can probably be done easily if I switch to using on_change.

  • Track unique users -> best way is to use cookies (e.g. with react-cookies) but this probably requires to show a consent form (could also build this in with react-cookie-consent)

  • Enable tracking on widgets created directly from container, expander, columns

  • Make a demo gif for the readme

  • Persist results after re-starting app (e.g. database or file, but where should this be saved/hosted)

  • Find an easier alternative to Firestore for saving the data

  • Track time the user spent in a session and show as "complete time spent on your app"

  • Implement A/B testing, e.g. by choosing one option for a new user randomly, storing it in session object, and then returning the correct bool value for below, and tracking & visualizing stats separately for both options:

    if streamlit_analytics.split_test("option a", 2):
        st.button("Is this button text better?")
    
    if streamlit_analytics.split_test("option b", 2):
        st.button("...or this one?")
  • Enable tracking to Google Analytics, e.g. via custom component with react-ga. Widget interactions could also be tracked via events.

  • Add a button to reset analytics results (see issue #2, this should probably show another prompt for confirmation, similar to if you delete a Github repo)

streamlit-analytics's People

Contributors

alyetama avatar jrieke avatar pedroprates avatar realisticitem avatar uranium2 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

streamlit-analytics's Issues

Add support for sliders / tuples in widget interactions

Almost all my widets are setup as double-sided sliders, which return a tuple of values. This produces an error in the widget interactions section of the report as tuples are not supported.

slider

I don't know what it would take to enable this, but I would like to have that functionality available to me.

Load Json to persist data between deployments

Hello,

I see you used firebase to do persist data between deployments, would it be possible to add this feature with json?

Context:

In my case the streamlit app is deployed on a docker and we have no persistence of volume (security constraint), so I was thinking about uploading the json on a server at fixed frequency rate. For example a task that will upload the json file each 30 seconds.

Then if the docker dies, we could fetch this json and load it in your tracker to persist the data.

streamlit-analytics does not load on deploy to share.streamlit.io

I really like the idea for this, as logging usage stats is one thing Streamlit is sorely lacking. Adding streamlit-analytics to my app works on my local machine

import streamlit_analytics

streamlit_analytics.start_tracking()
---code---
streamlit_analytics.stop_tracking()

but when copying to GitHub and deploying, the Streamlit share crashes and does not find the module streamlit_analytics. Is there something else I need to do to tell share.streamlit.io that I'm using this package? Your Live Demo is up, so it must be possible to deploy there.

Reset analytics

Hello! First of all thanks for this. It is really great to have something like built for Streamlit!

I have tried saving the analytics as JSON, and I have noticed that if I delete the .json file, once I run the app again I will still get my "old" analytics files. Is there a wy to reset the tracking and start from zero at some point?

track download_button

Tracking does not seem to be enabled for download_button. Would it be possible to add?

Timestamp data?

Hi, I got the JSON output but was wondering if there was a way to get the timestamp data for each input/run too?

Graph visualization sorts by months ignore year

We run streamlit_analytics since December and collect data successfully. The visualization however doesn't handle the year-switch well. The visualization aparently only sorts by month and ignores the year.

Bildschirmfoto 2024-01-23 um 09 30 39

Expected behaviour would be to start the graph have the graph between November '23 to Jan '24 in one line without wrap-around.

Tracking all select/multiselect options can consume too much memory

Currently this package tracks all select/multiselect options, even if they are not selected. This maybe is a good feature because the option list can change during the app utilization. Still, we can't track which option were available to each user.

On the other hand, this can be problematic when using more than, say, 3000 options. Or when the label changes in execution time. The visualization in the analytics section becomes too large and this has the potential of using a lot of memory, since counts are stored in session state. That is, it's necessary to allocate a dict with 3000 string keys.

Example:

import streamlit as st
import streamlit_analytics as st_analytics

with st_analytics.track():
    val = st.multiselect('abc', list(range(5000)))

One thing that's supposed to be desirable with this package is to interfere as little as possible with the server's resources or with the user's code. So I propose to create a parameter in start_tracking so the user can choose to track all options. But it should be False by default, in which case _wrap_select and _wrap_multiselect should behave like _wrap_value.

error with enqueue?

I packaged your 'minimal' example into a app() function (so I can run it as a page in a multi-page streamlit app). Similar to what's being done here.

import streamlit as st
import datetime

try:
    import streamlit_analytics
except ImportError:
    import subprocess, sys # Install streamlit-analytics on first run (not included in requirements.txt).
    subprocess.check_call([sys.executable, "-m", "pip", "install", "streamlit_analytics"])
    import streamlit_analytics

def app():
  with streamlit_analytics.track(verbose=True):
    st.text_input("Write your name")
    st.selectbox("Select your favorite", ["cat", "dog", "flower"])
    st.button("Click me")

However, I got the following error using streamlit 1.4

File "/home/appuser/venv/lib/python3.7/site-packages/streamlit/script_runner.py", line 379, in _run_script
    """
File "/app/k_internal_tools/app.py", line 23, in <module>
    apps.run()
File "/app/k_internal_tools/multiapp.py", line 64, in run
    functions[titles.index(title)]()
File "/app/k_internal_tools/apps/tracking.py", line 12, in app
    with streamlit_analytics.track(verbose=True):
File "/usr/local/lib/python3.7/contextlib.py", line 112, in __enter__
    return next(self.gen)
File "/home/appuser/venv/lib/python3.7/site-packages/streamlit_analytics/main.py", line 443, in track
    load_from_json=load_from_json,
File "/home/appuser/venv/lib/python3.7/site-packages/streamlit_analytics/main.py", line 282, in start_tracking
    last_time=datetime.datetime.now(),
File "/home/appuser/venv/lib/python3.7/site-packages/streamlit_analytics/session_state.py", line 92, in get
    (not hasattr(s, "_main_dg") and s.enqueue == ctx.enqueue)

Any ideas?

track with conditions

Hello, I have student project where I use your package. And I want to track information in if construction with elements which I create before that. Can I track information in same elements which I track information before use if or custom element.

Something like:
in code:

with streamlit_analytics.track():
    options = streamlit.multiselect(
        'Choose your color:',
        [
            '๐Ÿ”ด',
            '๐ŸŸก',
            '๐Ÿ”ต',
            '๐ŸŸข',
            '๐ŸŸฃ'
        ],
    button = streamlit.button('activate_button')
if button:
    streamlit_analytics.custom_track(name_of_element='activate_button')
    streamlit_analytics.custom_track(name_of_element='count_of_using_main_feature')

current result after choose ๐Ÿ”ด and ๐ŸŸก colors and click on button:

{
    'Choose your color:'
    {
        '๐Ÿ”ด': 3
        '๐ŸŸก': 2
        '๐Ÿ”ต': 0
        '๐ŸŸข': 0
        '๐ŸŸฃ': 0
    }
    'activate_button': 1
}

what I want to see:

{
    'Choose your color:'
    {
        '๐Ÿ”ด': 3
        '๐ŸŸก': 2
        '๐Ÿ”ต': 0
        '๐ŸŸข': 0
        '๐ŸŸฃ': 0
    }
    'activate_button': 2
    'count_of_using_main_feature': 1
}

Can you help me with that?

By the way, there is another point here. I think that bad if the checkbox has the value False, then it is tracking. And the fact that if I select elements in multiselect, it's all tracking sequentially as in the example above.

Error using your module

Screenshot 2021-04-27 at 13 38 48

I have this error when using your code, I had my own SessionState before but this error is after completely removing my call on session state.

Screenshot 2021-04-27 at 13 41 48

Any idea.
Thanks

KeyError when using select_slider

I am getting a key error at st.select slider

start_rating, end_rating = st.select_slider("Select range of CSAT", options=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], value=(2, 4))

KeyError: (2, 4)

Error on show_results

Hello, I'm trying to use your implementation and saving the results to a json file. It works fine when running on my local machine, but when deploying on a server I keep getting:
streamlit_analytics.stop_tracking(save_to_json='path', unsafe_password='somepass')
display.show_results(counts, reset_counts, unsafe_password)
metric() got an unexpected keyword argument 'help'.

I'm running on python 3.7, streamlit 1.9 and analytics 0.4.1.

Thanks for your time and attention.

ReportThread - ModuleNotFoundError

ReportThread seems to not be working with new version of streamlit (1.5.0.), I got ModuleNotFoundError. When I changed the source code in session_state.py and commented lines for importing report_thread and also disregarded ctx (ctx = ReportThread.get_report_ctx()) variable throughout the script I avoided error on local machine and everything worked fine (I could see basic statistics) but when I deploy it onto streamlit share framework error appeared once again.
Any thoughts on this?

Traffic plot improvement

  1. Initialize the traffic counts with yesterday is not right because it's misleading. If one doesn't know how the package works, he/she will think that the first day had no visitors.
  2. If the computer running the Streamlit server is in a negative timezone, altair/javascript will parse dates wrong and "shift" dates by -1 day. This Stack Overflow thread explains the issue.

See plot below to visualize the two problems:

image

Notice the first day is 0 and that the counting started in 27th august, but there are no pageviews in this day.

Issues with Session State

Hi guys,
First, congratulations for the great idea of creating an analytics tool for Streamlit.

I am having an issue when using Streamlit_analytics with a Login class for single user given as an argument within get_session_state

Is there any rule where to precisely include insert .start_tracking() and .stop_tracking() within a Streamlit app?

AttributeError: st.session_state has no attribute "last_time".

I get the following error:

AttributeError: st.session_state has no attribute "last_time". Did you forget to initialize it? More info: https://docs.streamlit.io/library/advanced-features/session-state#initialization

It comes from this line. The fix seems to be easy (avoid using the dot notation when the key doesn't exist)

    if "last_time" not in st.session_state:
        st.session_state.last_time = datetime.datetime.now()
    _track_user()

New widget support

Just wanted to say this module works so well and is exactly what I was looking for.

Just wondering if you plan to include tracking support for newer widgets (e.g st.chat_input) somewhere down the line?

Google Analytics integration

Request: To pass your analytics token into an optional function streamlit_analytics.google_analytics('gtag_ID') and allow the site owner to see the analytics use of their website

Additional Request: To enable alternative analytics providers in the same or similar fashion.

I am developing this on my fork for now
The purpose of this issue is to serve as a discussion point around integrations Google Analytics for any interested parties.

Firestore not playing nice with file_uploader

When one of the widgets is a file_uploader and Firestore is enabled, an error is thrown at stop_tracking complaining about something something not being a string something something.
(sorry, did not save the exact error anymore, I worked around it, see below).

This error does not appear when Firestore is not enabled.

I suspect this is because the return value of a file uploader is sometimes None and sometimes an instance of class UploadedFile, which is not a string. That causes a problem at seralization time into Firestore.

Workaround: I only call start_tracking() after having instanciated my file_uploader widget, meaning I cannot track any widget that comes before it. Sad, but the only way to make it work so far.

Alternative: adding a conditional to automatically convert the value of file_uploader into a string (containing maybe just the filename, not the whole uploaded file).

TypeError when displaying the tracking analytics

Got this issue when trying to displaying the tracking analytics with ?analytics=on. Any clues to fix this issue?

TypeError: metric() got an unexpected keyword argument 'help'
Traceback:
File "/usr/local/lib/python3.6/dist-packages/streamlit/scriptrunner/script_runner.py", line 554, in _run_script
    exec(code, module.__dict__)
File "/home/ubuntu/SE_github/streamlit_apps/dev_pages/csv_merger.py", line 22, in <module>
    "...and now add `?analytics=on` to the URL to see the analytics dashboard ๐Ÿ‘€"
File "/usr/lib/python3.6/contextlib.py", line 88, in __exit__
    next(self.gen)
File "/usr/local/lib/python3.6/dist-packages/streamlit_analytics/main.py", line 440, in track
    verbose=verbose,
File "/usr/local/lib/python3.6/dist-packages/streamlit_analytics/main.py", line 405, in stop_tracking
    display.show_results(counts, reset_counts, unsafe_password)
File "/usr/local/lib/python3.6/dist-packages/streamlit_analytics/display.py", line 44, in show_results
    help="Every time a user (re-)loads the site.",

Saving to Firestore fails for `None` values

Hi!
Quite excited at the prospect of using streamlit-analytics, but with streamlit-analytics 0.2.2 I get a bad error when calling

sa.stop_tracking(firestore_key_file="firestore-key.json", firestore_collection_name="counts") 

The following error gets displayed in my app:

TypeError: '<' not supported between instances of 'str' and 'NoneType'
Traceback:
File "/opt/conda/lib/python3.6/site-packages/streamlit/script_runner.py", line 337, in _run_script
    exec(code, module.__dict__)
File "/root/xray/web_demo_streamlit.py", line 244, in <module>
    firestore_collection_name="counts")
File "/opt/conda/lib/python3.6/site-packages/streamlit_analytics/main.py", line 381, in stop_tracking
    firestore.save(counts, firestore_key_file, firestore_collection_name)
File "/opt/conda/lib/python3.6/site-packages/streamlit_analytics/firestore.py", line 24, in save
    doc.set(counts)  # creates if doesn't exist
File "/opt/conda/lib/python3.6/site-packages/google/cloud/firestore_v1/document.py", line 166, in set
    batch, kwargs = self._prep_set(document_data, merge, retry, timeout)
File "/opt/conda/lib/python3.6/site-packages/google/cloud/firestore_v1/base_document.py", line 211, in _prep_set
    batch.set(self, document_data, merge=merge)
File "/opt/conda/lib/python3.6/site-packages/google/cloud/firestore_v1/base_batch.py", line 98, in set
    reference._document_path, document_data
File "/opt/conda/lib/python3.6/site-packages/google/cloud/firestore_v1/_helpers.py", line 697, in pbs_for_set_no_merge
    extractor = DocumentExtractor(document_data)
File "/opt/conda/lib/python3.6/site-packages/google/cloud/firestore_v1/_helpers.py", line 497, in __init__
    for field_path, value in iterator:
File "/opt/conda/lib/python3.6/site-packages/google/cloud/firestore_v1/_helpers.py", line 444, in extract_fields
    for s_path, s_value in extract_fields(value, field_path):
File "/opt/conda/lib/python3.6/site-packages/google/cloud/firestore_v1/_helpers.py", line 444, in extract_fields
    for s_path, s_value in extract_fields(value, field_path):
File "/opt/conda/lib/python3.6/site-packages/google/cloud/firestore_v1/_helpers.py", line 434, in extract_fields
    for key, value in sorted(document_data.items()):

Any idea what I'm missing, please?

Please open new issues in streamlit-analytics2

Hi All
As I understand that this project is used by many people but its not currently possible to collaborate and address issues
Thank you for Johannes for all your contributions and effort until now

Please consider opening any new issues to a fork of this repo, where we hope to fix certain issues collaboratively

Please note that the existing main branch already contains 25 security fixes and resolves the deprecation notice on query parameters

None Selected

Hello,

Defining a selectbox like this :
select = st.selectbox("title", ("A", "B"), index=None)
... I get a KeyError exception at main.py:162
This is caused by the default "None" value. It is changed into an empty space by "replace_empty(...)", but
if selected != st.session_state.state_dict.get(label, None): goes into the "true" statement, and causes an exception.

The fix at main.py:161:
if selected != st.session_state.state_dict.get(label, " "):

I make a PR or let you fix ...

Tracking buttons with the same name

Hi, awesome tool!

I have a use case where several buttons have the same text, and currently the analytics tool tracks all of them as a single button. Each of the buttons has a unique key, and I thought it could be useful to support key based indexing instead of, e.g., button label based.
Is this possible in streamlit-analytics?

Thanks!

Option to load firestore key from dict

I am trying to deploy an app & I do not want to publicly show my firebase key

For this I use st.secrets to make a dict object, but there is no option to pass it to the track function

support for multipage - fork / wrap st.title?

Hi,

I want to have some analytics for my multipage app. I forked @franasol's fork and took a look. It seems as if the way to proceed is to create wrapper functions for the page filename and st.title, then feed the results into a new object that is compatible with the other counting objects. Is that more or less correct? Any gotchas?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.