Week 11 - Dashboarding

Introduction to Dashboarding

Metabase Setup

Dashboards in Metabase

Streamlit Fundamentals

Building a Metrics Dashboard

Presenting Metrics

Practice

Gotchas & Pitfalls

Assignment: Build Two Dashboards

Slides (PDF)

Career relevance: Week 11

Glossary: Week 11

Going Further

History of Dashboarding

Gotchas & Pitfalls

Read this before you submit the assignment. These are the eight mistakes that appear most often in Week 11 work, each with the cause and the fix.

1. Using a read-write user for Metabase

What happens: You connect Metabase to Postgres using the same write-capable pipeline_user your ingestion and dbt builds use.

Why it matters: pipeline_user has INSERT, UPDATE, and DELETE permissions. A misconfigured Metabase Question or a future plugin bug could inadvertently write to your mart tables, silently corrupting the data your dashboards are reading.

Fix: Use metabase_user: a read-only role with SELECT-only permissions on your schema. If you are not sure which user Metabase is connecting with, ask your teacher to check the admin settings.

2. Hardcoding credentials in Python

What happens: You put the Postgres password directly in app.py.

# ❌ This ends up in git
engine = sqlalchemy.create_engine("postgresql://pipeline_user:MyPassword123@...")

Why it matters: Once a credential is in a git commit, it is in git history forever, even if you delete it in the next commit. If the repo becomes public, the credential is exposed.

Fix: Use environment variables or a .env file excluded from git:

# ✅ Correct
import os
engine = sqlalchemy.create_engine(os.environ["POSTGRES_URL"])

Add .env to your .gitignore before your first commit.

3. No @st.cache_data on database queries

What happens: Every widget click (date picker, selectbox, checkbox) reruns the full Streamlit script, including every pd.read_sql() call. With a 2-second query, a dashboard that rerenders 10 times per minute makes 20 database round-trips per minute, or 1,200 per hour.

Fix: Wrap every database call in @st.cache_data(ttl=...):

@st.cache_data(ttl=300)
def load_trips():
    ...

Pick your TTL based on how often the data actually changes: 60 seconds for a freshness check, 3600 seconds for daily mart snapshots.

4. Empty dashboard: wrong schema or unbuilt mart

What happens: Your Streamlit app runs but every panel is empty or zero, or you get UndefinedTable: relation "…fct_trips" does not exist.

Why it matters: The dashboard reads {DB_SCHEMA}.fct_trips. If DB_SCHEMA does not match the dev_<name> schema where your Week 10 dbt build wrote the mart, the query hits the wrong (or a non-existent) table and silently returns nothing.

Fix: Confirm two things before debugging the app:

-- 1. Does the mart exist in your schema, and does it have rows?
SELECT COUNT(*) FROM dev_yourname.fct_trips;
# 2. Does the app's DB_SCHEMA match? Check the .env the app reads, not the shell.
grep DB_SCHEMA .env      # must be your dev_<name>

If the count is zero, rebuild the mart from your Week 10 project with dbt build. If the schema is wrong, fix DB_SCHEMA in your .env. (echo $DB_SCHEMA prints nothing here: python-dotenv loads .env into the app's process, not your shell.) Also make sure your connection string ends with ?sslmode=require, or Azure Postgres refuses the connection entirely.

5. Metabase date filter connecting to the wrong column

What happens: You add a date-range filter to your Dashboard, but one panel updates and the other ignores the selection entirely.

Why it matters: A dashboard filter can only reach a native SQL Question through a Field Filter variable in that Question's SQL. If one Question has WHERE {{pickup_date}} wired up and the other does not, Metabase has nothing to connect the filter to on the second panel, so it silently does nothing. This is about the missing variable, not the column type.

Fix: Give every Question that should respond to the filter a Field Filter (WHERE {{pickup_date}} in SQL mode), then map the dashboard filter to each one. A Question with no Field Filter, such as a borough bar chart that aggregates over all time, will not react and is not meant to.

6. Assuming a time zone that isn't there

What happens: Your freshness panel shows MAX(pickup_datetime) and you "fix" it by treating it as UTC and converting to your local time. Now every timestamp is off by hours.

Why it matters: In the class mart, pickup_datetime is a timestamp without time zone: a naive wall-clock value in the source's own zone (New York local time for the NYC taxi data), with no zone attached. Postgres stores it exactly as ingested and converts nothing to UTC. If you label it UTC and tz_convert it to Amsterdam, you shift a NYC time by hours and every displayed value is wrong.

Fix: Know your source's time zone before touching it, and display the value as-is unless you have a real reason to convert. Only attach and convert zones when you actually know the source zone:

import pandas as pd

# pickup_datetime is naive NYC-local wall-clock time: display it as-is.
last = pd.to_datetime(fresh["last_pickup"])
st.metric("Last pickup (NYC)", last.strftime("%Y-%m-%d %H:%M"))

Always state the time zone in the panel label so nobody has to guess.

7. Dashboard showing too many panels

What happens: You add 10+ panels to your Metabase dashboard because you built 10 Questions. The dashboard takes 8 seconds to load (10 parallel SQL queries) and nobody knows where to look first.

Fix: A dashboard is not a data dump. Limit it to 5-7 panels that tell a coherent story. Move supporting panels to a separate "Detail" dashboard that stakeholders can drill into. The assignment minimum is 3 panels. Add more only if each one adds a distinct insight.

8. Undefined metrics in the presentation

What happens: You show "quality score: 87%" during the presentation. Someone asks "what is a quality score?" and you do not have an answer.

Why it matters: An undefined metric destroys trust in the number. If the audience cannot evaluate whether 87% is good or bad, the metric is useless to them.

Fix: Write a metric definition for every panel before the presentation. The five fields (name, description, calculation, data source, refresh frequency) should be in your metric_definitions.md file and visible to the audience when you present. If you cannot define it, remove the panel.


The HackYourFuture curriculum is licensed under CC BY-NC-SA 4.0 *https://hackyourfuture.net/*

CC BY-NC-SA 4.0 Icons

Built with ❤️ by the HackYourFuture community · Thank you, contributors

Found a mistake or have a suggestion? Let us know in the feedback form.