Measure Contribution Guidelines

General workflow#

  • Every feature or bug must have a task in the project board
  • Each task gets converted to an issue
  • Pull requests must be opened against an existing issue (which in turn contains a linked task in the board)
  • All pull requests must be reviewed and approved by at least 1 maintainer before merging

Local environment setup#

[!TIP]

If you just looking to try out measure, follow our self hosting guide.

The self hosting guide is the official and recommended way to try out measure.

Prerequisites#

  • Docker >= v26.1.3
  • Docker Compose >= 2.27.3
  • Node LTS

After cloning the repository, run the following commands for the best contribution experience. All core maintainers MUST follow these steps.

[!NOTE]

You would need node to run the above commands. We recommend you always stick to the lts version of node. If you need to setup node, we recommend you use fnm (Fast Node Manager) to manage node versions. Follow fnm's installation instructions.

In the repo root, run

npm install
npm run prepare

The above commands would install the required dependencies and setup git hooks as intended. This is a one-time setup, unless you do a fresh clone again.

Setup initial configuration#

Change to self-host directory.

cd self-host

Next, configure the environment variables.

./config.sh --development --wizard

This will start the configuration wizard and prepare all the environment variable files tuned for local development.

Start services#

Once configuration is complete, run the following docker compose command to start all services. For starting for the first time, provide --profile migrate to trigger database migrations.

docker compose --profile migrate up

[!NOTE]

About Compose Profiles

The migrate profiles are idempotent in nature. You can use it every time, though for subsequent runs you may choose to skip them.

Alternatively, you could build and up the containers in separate steps, like this.

docker compose build
docker compose --profile migrate up

Additionally, run the following script to finish migration.

./migrations/v0.9.x-data-backfills.sh

For automatic file watching using docker compose, run:

docker compose watch
# or
docker compose up --watch

Shutdown services#

To stop all services and to remove all containers, run.

docker compose --profile migrate stop
docker compose --profile migrate down

Troubleshooting#

In case of any issues related to incoherent state, reset your environment by running. Keep in mind that this will remove all Measure volumes and all the data contained in those volumes.

# would stop all containers and
# remove images, orphan containers, volumes & networks
docker compose down --rmi all --remove-orphans --volumes

And rerun.

docker compose --profile migrate up

Running Tests#

Backend#

Backend Go tests are separated into three categories using build tags.

Unit tests — pure logic, no containers or external services needed:

cd backend/api && go test ./...

Integration tests — require Docker (spins up Postgres and ClickHouse via testcontainers):

cd backend/api && go test -tags=integration ./...
cd backend/billing && go test -tags=integration ./...
cd backend/email && go test -tags=integration ./...

Functional tests — end-to-end Stripe billing cycle tests. These are only relevant for core developers working on billing. They require a Stripe test environment with the following environment variables:

  • TEST_STRIPE_API_KEY — Stripe API key for the test environment
  • TEST_STRIPE_UNIT_DAYS_METER_NAME — event name of the billing meter
  • TEST_STRIPE_PRO_UNIT_DAYS_PRICE_ID — price ID for the pro plan

Note that functional tests take more time than Go's default 10m timeout to run so a custom timeout should be passed in.

go test backend/... -tags=functional ./... -timeout 1h -v

Run all backend tests together without cache:

go test backend/... --tags=integration,functional -timeout 1h -v -count=1

Frontend Dashboard#

cd frontend/dashboard && npm run test

Android SDK#

cd android/
./gradlew :measure:testDebugUnitTest

Flutter SDK#

cd flutter/
melos test:all

iOS SDK#

cd ios/
xcodebuild test \
    -project ios/MeasureSDK.xcodeproj \
    -scheme MeasureSDKTests \
    -sdk iphonesimulator \
    -destination 'platform=iOS Simulator,name=iPhone 16 Pro,OS=18.4' \
    ONLY_ACTIVE_ARCH=YES

Writing commit messages#

All commits landing in any branch are first linted in your local environment and then in CI.

  • Follow Conventional Commits for all commits.
  • Stick to present tense for the commit message language
  • Follow these type rules
    • fix: for commits that fixes a bug and must bump semver PATCH.
    • feat: for commits that adds new features and must bump semver MINOR
    • docs: for commits that modifies user facing documentation
    • ci: for commits that modifies CI configuration
    • chore: for commits that modifies settings, configurations and everything else
  • Scoping your commits is optional, but encouraged. Allowed scopes are:
    • ios for commits related to iOS SDK
    • android for commits related to Android SDK
    • frontend for commits related to dashboard web app
    • backend for commits related to backend infrastructure
    • and more
  • Try not to exceed 72 characters for commit header message
  • Try not to exceed 100 characters for each line in body. Break each line with newlines to remain under 100 characters.
  • Make sure commit message headers are in lowercase
  • Make sure commit message body & footer are always sandwiched with a single blank line

❌ Bad Commits#

  • No type

    fix an issue with session replay
    
  • Incorrect scope

    feat(foobar): add exception symbolication
    
  • No newline between header & body

    feat(backend): add exception symbolication
    Add android symbolication of unhandled exceptions
    
  • Exceeding body-max-line-length

    fix(backend): frames not ingesting
    
    this is a really really really long line that is exceeding the allowed limit of max characters per line
    

✅ Good Commits#

  • Correct type

    fix: an issue with session replay
    
  • Correct & allowed scope

    feat(backend): add exception symbolication
    
  • 1 blank line between header & body

    feat(backend): add exception symbolication
    
    Add android symbolication of unhandled exceptions
    
  • Each body line is within limits

    fix(backend): frames not ingesting
    
    this is a really really really long line that is
    exceeding the allowed limit of max characters per line
    

Managing databases#

When contributing to databases, please strictly follow the following guidelines.

  • Ensure every migration is backward compatible.
  • Optimize queries for performance and scalability.
  • Make sure all database migrations are ALWAYS in sequence.

Connecting to Postgres#

To connect to locally running Postgres instance, use the following command from the self-host directory:

# when postgres service is running
docker compose exec postgres psql -U postgres -d measure

# when postgres service is _not_ running
docker compose run --rm postgres psql -U postgres -d measure

Connecting to ClickHouse#

To connect to locally running ClickHouse instance, use the following command from the self-host directory:

# when clickhouse service is running
docker compose exec clickhouse clickhouse-client -u app_admin -d measure

# when clickhouse service is _not_ running
docker compose run --rm clickhouse clickhouse-client -u app_admin -d measure

Managing Dashboard Environment Variables#

Typically, there are 2 kinds of environment variables in the dashboard nextjs application. Public & Private.

  1. Public. Variables prefixed with NEXT_PUBLIC_...

    • Public variables MUST only contain non-sensitive data.
    • Public variables are baked into the deployable artifact at build time.
    • Public variables are exposed to browsers which is a vulnerable environment.
    • Example: OAuth client identifiers, analytics service identifers & so on.

    To manage public variables:

    1. Define them in the dashboard/compose.prod.yml file under dashboard.build.args section.
    2. Define them in the dashboard/dockerfile.prod file as ARG & pass them as environment variables in the RUN directive.
    3. Make sure to keep the variables in sync in both step 1 & 2.

    Example for dashboard/compose.prod.yml

    dashboard:
      build:
        dockerfile: dockerfile.prod
        args:
          - NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL}
          - NEXT_PUBLIC_YOUR_VARIABLE=${NEXT_PUBLIC_YOUR_VARIABLE}
    

    Example for dashboard/dockerfile.prod

    ARG NEXT_PUBLIC_SITE_URL
    ARG NEXT_PUBLIC_YOUR_VARIABLE
    
    RUN NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL} \
        NEXT_PUBLIC_YOUR_VARIABLE=${NEXT_PUBLIC_YOUR_VARIABLE} \
        npm run build
    
  2. Private. Variables without the NEXT_PUBLIC_... prefix

    • Private variables may contain sensitive & non-sensitive data.
    • Private variables are not baked into the deployable artifact.
    • Private variables are not exposed to browsers, they are safely & securely passed to the nextjs server at runtime.
    • Exmaple: OAuth client secrets, LLM provider API keys & other non-sensitive data.

    To manage private variables:

    1. Define them in the self-host/.env.
    2. Use them in the self-host/compose.yml under services.dashboard.environment.

    Example for self-host/compose.yml

    serivces:
      dashboard:
        environment:
          - YOUR_VARIABLE=${YOUR_VARIABLE}
    

[!CAUTION]

NEVER commit the self-host/.env file.

Migrating codebase from <= v0.8.x#

  1. Turn off all services

    # run from self-host directory
    docker compose down
    
  2. Migrate configurations

    ./config.sh --development --ensure
    
  3. Synchronize databases

    ./migrations/v0.9.x-sync-databases.sh
    
  4. Run database migrations

    docker compose run --rm dbmate-postgres migrate
    
    docker compose run --rm dbmate-clickhouse migrate
    
  5. Run backfills script

     ./migrations/v0.9.x-data-backfills.sh
     ./migrations/v0.10.x-read-optim.sh
    
  6. Start development

    docker compose watch
    
  7. Additionally, copy postgres_dsn and clickhouse_dsn variables in sessionator config.toml from config.toml.example

    postgres_dsn = "postgresql://..."
    clickhouse_dsn = "clickhouse://..."
    

Release process#

To trigger a release, create a signed git tag using git-cliff and push the tag. Here's a one liner.

# bash/zsh
VERSION=$(git cliff --bumped-version) && git tag -s $VERSION -m $VERSION && git push origin $VERSION

# fish
set VERSION $(git cliff --bumped-version) && git tag -s $VERSION -m $VERSION && git push origin $VERSION

Documentation#

  • Public facing docs should be in docs folder - API requests & responses, self host guide, SDK guides and so on
  • Main folder of subproject should link to main guide. ex: frontend README has link to self hosting and local dev guide
  • Non public facing docs can stay in sub folder. ex: backend benchmarking README which describes its purpose
  • Docs pages, sidebar nav, search index, and sitemap are all auto-generated at build time from the markdown files. The sidebar nav is derived from the link structure in docs/README.md — ordering and grouping come from the links there, and titles come from each page's # H1 heading. To add a new doc page, create the .md file and add a link to it in the README