Mirror of https://github.com/roostorg/osprey github.com/roostorg/osprey
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Docs: Restructure, simplify, and set up mdbook (#117)

authored by

Cassidy James Blaede and committed by
GitHub
a9b9f7ee 7533c23b

+470 -494
+56
.github/workflows/mdbook.yml
··· 1 + name: Deploy mdBook site to Pages 2 + 3 + on: 4 + # Runs on pushes targeting the default branch 5 + push: 6 + branches: ["main"] 7 + 8 + # Allows you to run this workflow manually from the Actions tab 9 + workflow_dispatch: 10 + 11 + # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 12 + permissions: 13 + contents: read 14 + pages: write 15 + id-token: write 16 + 17 + # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 18 + # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 19 + concurrency: 20 + group: "pages" 21 + cancel-in-progress: false 22 + 23 + jobs: 24 + # Build job 25 + build: 26 + runs-on: ubuntu-latest 27 + env: 28 + MDBOOK_VERSION: 0.5.2 29 + steps: 30 + - uses: actions/checkout@v4 31 + - name: Install mdBook 32 + run: | 33 + curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf -y | sh 34 + rustup update 35 + cargo install --version ${MDBOOK_VERSION} mdbook 36 + - name: Setup Pages 37 + id: pages 38 + uses: actions/configure-pages@v5 39 + - name: Build with mdBook 40 + run: mdbook build docs 41 + - name: Upload artifact 42 + uses: actions/upload-pages-artifact@v3 43 + with: 44 + path: ./docs/book 45 + 46 + # Deployment job 47 + deploy: 48 + environment: 49 + name: github-pages 50 + url: ${{ steps.deployment.outputs.page_url }} 51 + runs-on: ubuntu-latest 52 + needs: build 53 + steps: 54 + - name: Deploy to GitHub Pages 55 + id: deployment 56 + uses: actions/deploy-pages@v4
+3
.gitignore
··· 306 306 # End of https://www.toptal.com/developers/gitignore/api/python,rust,pycharm 307 307 308 308 .claude 309 + 310 + # docs output 311 + book/
+22 -99
README.md
··· 1 - # ROOST - Osprey 2 - 3 - <img width="200" height="64" alt="Copy of ROOST-Mark-Yellow" src="/images/ROOST-Horizontal-Yellow.png" /> 4 - 5 - # Welcome to Osprey 6 - 7 - <img alt="Osprey UI Sample" src="./images/query-and-charts.png" /> 8 - 9 - 10 - ## Overview 1 + <img width="200" height="64" alt="ROOST logo" src="images/ROOST-Horizontal-Yellow.png" /> 11 2 12 - This repository is home to one of ROOST’s safety tools, the Osprey rules engine. Osprey is an open-source event stream decisions engine and analysis UI designed to investigate and take automatic action on events and their properties as they happen in real-time. Originally developed internally at [Discord](https://discord.com/) to combat spam, abuse, botting, and scripting across its platform, Osprey has now been open-sourced to help other platforms facing similar challenges. 3 + # Osprey 13 4 14 - Osprey is a library for processing actions through human-written rules and outputting verdicts & custom effects back to configurable output sinks. It evaluates events using structured rule logic (SML) that is extendable via user-defined functions (UDFs). Osprey can also track state across events by labelling entities if implementers provide a labels service backend (see [labels_service.py](./example_plugins/src/services/labels_service.py) for a Postgres-backed labels service example) 15 - 16 - This 'Rules \+ Investigation' tool is able to: 5 + **Automate the obvious and investigate the ambiguous.** High-performance safety rules engine for real-time event processing at scale. 17 6 18 7 - take action based on user behavior 19 8 - combine actions with human written rules 20 9 - let operators query human actions and past decisions 21 10 - perform investigations or write new rules based on decisions 22 11 23 - ## v0 Release Notes 24 - The v0 release is built for engineers and Trust & Safety teams who want to explore, test, and integrate Osprey's core capabilities into their platform for incident response and Trust & Safety investigation. 12 + Osprey is an event stream decisions engine and analysis UI designed to investigate and take automatic action on events and their properties as they happen in real-time. Originally developed internally at [Discord](https://discord.com/) to combat spam, abuse, botting, and scripting across its platform, Osprey has been open-sourced to help other platforms facing similar challenges. 25 13 26 - Some UI features have been planned for the v1 release to ensure a solid foundation for experimentation: 14 + ![Osprey UI sample](docs/images/query-and-charts.png) 27 15 28 - - **Bulk actions** - Mass investigation and response operations through the UI 29 - - **Labels** - Visual entity tagging and categorization systems in the interface 30 - - **Event stream in UI** - Real-time event monitoring and alert dashboard 31 - - **Load Balancing** - Better performance efficiency and management of sync/async rules 16 + Osprey is a library for processing actions through human-written rules and outputting verdicts & custom effects back to configurable output sinks. It evaluates events using structured rule logic (SML) that is extendable via user-defined functions (UDFs). Osprey can also track state across events by labelling entities if implementers provide a labels service backend (see [labels_service.py](./example_plugins/src/services/labels_service.py) for a Postgres-backed labels service example). 32 17 33 - ### Feedback Wanted 34 - This is a working system, not a prototype. Try it locally, connect your data, write some rules, and tell us what's missing for your use case. We're particularly interested in: 18 + Osprey is built for engineers and Trust & Safety teams who want to explore, test, and integrate its core capabilities into their platform for incident response and Trust & Safety investigation. [Read more about user research and personas](docs/user_personas.md). 35 19 36 - - Integration challenges with your existing platform infrastructure 37 - - Performance characteristics with your event volumes and rule complexity 38 - - Missing detection capabilities or response actions you need 39 - - API improvements that would make adoption easier for your team 20 + ## Development 40 21 41 - Your experimentation feedback will directly shape v1 priorities and help us build the most useful Trust & Safety tooling for the community. 22 + - See [DEVELOPMENT.md](./docs/DEVELOPMENT.md) for comprehensive development setup and workflow documentation 23 + - All code changes should pass linting (Ruff) and type checking (MyPy) 24 + - Pre-commit hooks automatically run on each commit to maintain code quality 42 25 43 26 ## Join Us 27 + 44 28 Writing code is not the only way to help the project. Reviewing pull requests, answering questions to help others on mailing lists or issues, providing feedback from a domain expert perspective, organizing and teaching tutorials, working on the website, improving the documentation, are all priceless contributions. 45 29 46 - - Join us on [Discord server](https://discord.gg/5Csqnw2FSQ) 30 + - Join us in [our Discord server](https://discord.gg/5Csqnw2FSQ) 47 31 - Join our [newsletter](https://roost.tools/#get-started) for more announcements and information 48 32 - Follow us on [Bluesky](https://bsky.app/profile/roost.tools) or [LinkedIn](https://www.linkedin.com/company/roost-tools/) 49 33 50 - _ROOST (Robust Open Online Safety Tools), a non-profit organization that brings together expertise, resources, and investments from major technology companies and philanthropies to build scalable, interoperable safety infrastructure for the AI era._ 34 + _[ROOST](https://roost.tools) (Robust Open Online Safety Tools) is a non-profit organization that brings together expertise, resources, and investments from major technology companies and philanthropies to build scalable, interoperable safety infrastructure for the AI era._ 51 35 52 - ## 🚀 Quick Start 36 + ### Feedback Wanted 53 37 54 - ### Prerequisites 38 + This is a working system, not a prototype. Try it locally, connect your data, write some rules, and tell us what's missing for your use case. We're particularly interested in: 55 39 56 - - Python 3.11 or higher 57 - - Git 58 - - [uv](https://docs.astral.sh/uv/) (Python package manager) or python package manager of choice 40 + - Integration challenges with your existing platform infrastructure 41 + - Performance characteristics with your event volumes and rule complexity 42 + - Missing detection capabilities or response actions you need 43 + - API improvements that would make adoption easier for your team 59 44 60 - ### Installation 45 + Your experimentation feedback will directly shape future priorities and help us build the most useful Trust & Safety tooling for the community. 61 46 62 - 1. **Clone the repository:** 47 + ## Recognition 63 48 64 - ```bash 65 - git clone git@github.com:roostorg/osprey.git 66 - cd osprey 67 - ``` 68 - 69 - 2. **Install dependencies using uv:** 70 - 71 - ```bash 72 - uv sync 73 - ``` 74 - 75 - 3. **Set up development tools:** 76 - 77 - ```bash 78 - uv run pre-commit install 79 - ``` 80 - 81 - 4. **Verify setup:** 82 - 83 - ```bash 84 - # Test linting 85 - uv run ruff check 86 - uv run mypy . 87 - 88 - # Test formatting 89 - uv run ruff format --diff 90 - 91 - # Test pre-commit hooks 92 - uv run pre-commit run --all-files 93 - 94 - 5. **Start Services:** 95 - 96 - ```bash 97 - docker compose up -d 98 - ``` 99 - 100 - or using the wrapper script 101 - 102 - ```bash 103 - ./start.sh 104 - ``` 105 - 106 - this starts the osprey-worker on its own along with all its required dependencies. 107 - 108 - alternatively, you can start Osprey with `osprey-coordinator`, refer to the [Coordinator README](./example_docker_compose/run_osprey_with_coordinator/README.md) for more information 109 - 110 - 6. (Optional) **Open ports for the UI/UI API:** 111 - 112 - By default, the `docker-compose.yaml` binds running services to `127.0.0.1`. If you are running the docker compose on a headless machine, you may need to modify this configuration and/or make changes to your firewall, specifically for ports `5002` and `5004`. 113 - 114 - For example, if you use Tailscale to access your Osprey instance, you may change `127.0.0.1:5002:5002` to `<Tailscale IP>:5002:5002`. Alternatively, if you wish for your instance to be accessible from the public internet, you may set it simply to `5002:5002` to bind to `0.0.0.0`. 115 - 116 - Be aware that some firewalls like iptables/UFW do _not_ prevent access to ports being used by Docker networking. Not explicitly setting a bind address with only UFW as a firewall will not prevent access from the public internet unless [properly configured](https://github.com/chaifeng/ufw-docker). 117 - 118 - ### Development Workflow 119 - 120 - - See [DEVELOPMENT.md](./docs/DEVELOPMENT.md) for comprehensive development setup and workflow documentation 121 - - All code changes should pass linting (Ruff) and type checking (MyPy) 122 - - Pre-commit hooks automatically run on each commit to maintain code quality 123 - 124 - 125 - ## Recognition 126 49 Discord uses Osprey to quickly detect and remove new types of harm that put users at risk. Rather than leaving other platforms to build similar tools from scratch, ROOST and Discord have open-sourced this powerful rule engine in collaboration with [internet.dev](https://internet.dev/) to make it available for anyone who needs it.
+15
book.toml
··· 1 + [book] 2 + title = "Osprey Documentation" 3 + authors = ["ROOST"] 4 + language = "en" 5 + src = "docs" 6 + 7 + [build] 8 + build-dir = "book" 9 + 10 + [output.html] 11 + edit-url-template = "https://github.com/roostorg/osprey/edit/main/{path}" 12 + git-repository-url = "https://github.com/roostorg/osprey" 13 + 14 + [output.html.print] 15 + enable = false
+30 -325
docs/DEVELOPMENT.md
··· 1 - # Development 2 - Welcome to the development guide for Osprey. This document will help you get started with contributing to the project. 3 - 4 - ## Reporting a Bug or Issue 5 - Found a bug or have a feature request? We'd love to hear from you! When opening an issue, please use our templates: 6 - 7 - * [Bug Report](https://github.com/roostorg/osprey/issues/new?template=bug_report.md) 8 - * [Feature Request](https://github.com/roostorg/osprey/issues/new?template=feature_request.md) 9 - * [Submit an Egg (new tool idea) to ROOST!](https://github.com/roostorg/osprey/issues/new?template=documentation.md) 10 - 11 1 # Setup Guide 12 2 13 3 This guide provides comprehensive instructions for setting up a development environment for Osprey. 14 4 15 5 ## Prerequisites 16 6 17 - ### System Requirements 18 - 19 - - **Python 3.11 or higher** - Check with `python --version` 20 - - **Git** - Version control system 21 7 - **Operating System**: macOS, Linux, or Windows (with WSL recommended) 22 - 23 - ### Package Manager: UV 24 - 25 - Osprey uses [uv](https://docs.astral.sh/uv/) as the Python package manager for fast, reliable dependency management. 26 - 27 - #### Install UV 28 - 29 - **macOS/Linux:** 30 - 31 - ```bash 32 - curl -LsSf https://astral.sh/uv/install.sh | sh 33 - ``` 34 - 35 - **Windows:** 36 - 37 - ```bash 38 - powershell -c "irm https://astral.sh/uv/install.ps1 | iex" 39 - ``` 40 - 41 - **Alternative (via pip):** 42 - 43 - ```bash 44 - pip install uv 45 - ``` 46 - 47 - **Verify installation:** 48 - 49 - ```bash 50 - uv --version 51 - ``` 8 + - **[Python](https://www.python.org/) 3.11 or higher** (check with `python --version`) 9 + - **[Git](https://git-scm.com/)** for version control 10 + - **[uv](https://docs.astral.sh/uv/)** for Python package management 11 + - **[npm](https://nodejs.org/en/download)** 52 12 53 13 ## Project Setup 54 14 ··· 113 73 docker compose up -d 114 74 ``` 115 75 76 + or using the wrapper script 77 + 78 + ```bash 79 + ./start.sh 80 + ``` 81 + 116 82 This starts up many services, including: 117 83 - **Osprey Worker**: The main engine that processes input events given the rules and UDFs 118 84 - **Test Data Producer**: Optional with `--profile test_data` ··· 122 88 - **Postgres**: A database that the Worker, UI API, and Druid use for various reasons, such as the Postgres-backed Labels Service (in the example plugins) 123 89 - **Druid**: A database that consumes Osprey Worker outputs to power the UI API for real-time querying 124 90 91 + Alternatively, you can start Osprey with `osprey-coordinator`, refer to the [Coordinator README](./example_docker_compose/run_osprey_with_coordinator/README.md) for more information 125 92 126 - ### 6. Access the Application 127 - The UI will automatically connect to the backend services running in Docker containers. 93 + ### 6. (Optional) Open ports for the UI/UI API 94 + 95 + By default, the `docker-compose.yaml` binds running services to `127.0.0.1`. If you are running the docker compose on a headless machine, you may need to modify this configuration and/or make changes to your firewall, specifically for ports `5002` and `5004`. 128 96 129 - - Osprey UI: http://localhost:5002 130 - - Backend API: http://localhost:5004 131 - - Worker Service: http://localhost:5001 97 + For example, if you use Tailscale to access your Osprey instance, you may change `127.0.0.1:5002:5002` to `<Tailscale IP>:5002:5002`. Alternatively, if you wish for your instance to be accessible from the public internet, you may set it simply to `5002:5002` to bind to `0.0.0.0`. 132 98 99 + Be aware that some firewalls like iptables/UFW do _not_ prevent access to ports being used by Docker networking. Not explicitly setting a bind address with only UFW as a firewall will not prevent access from the public internet unless [properly configured](https://github.com/chaifeng/ufw-docker). 133 100 134 - #### Plugins 101 + ### 7. Access the Application 102 + 103 + The UI will automatically connect to the backend services running in Docker containers. 104 + 105 + - Osprey UI: [localhost:5002](http://localhost:5002) 106 + - Backend API: [localhost:5004](http://localhost:5004) 107 + - Worker Service: [localhost:5001](http://localhost:5001) 108 + 109 + ## Plugins 135 110 136 111 In Osprey, UDFs and output sinks are designed to be easily portable. This is done through a plugin system based on pluggy. An example plugin package has been provided for reference, see `example_plugins/register_plugins.py`: 137 112 ··· 150 125 # Register AST validators 151 126 ``` 152 127 153 - #### Rules 128 + ## Rules 154 129 155 130 Rules are written in SML, some examples are provided in `example_rules/` with YAML config, the rules are mounted to the worker processes when the containers start via environment variables. ex: 156 131 ··· 158 133 OSPREY_RULES=./example_rules uv run python3.11 osprey_worker/src/osprey/worker/cli/sinks.py run-rules-sink 159 134 ``` 160 135 161 - #### Test Data 136 + [More about rules →](rules.md) 137 + 138 + ## Test Data 162 139 163 140 Generate sample JSON actions: 164 141 ```bash 165 - docker compose --profile test_data up kafka-test-data-producer -d 142 + docker compose --profile test_data up osprey-kafka-test-data-producer -d 166 143 ``` 167 144 168 145 Produces user login events with timestamps, user IDs, and IP addresses to `osprey.actions_input` topic. 169 - 170 - ## Development Workflow 171 - 172 - ### Branch Management 173 - 174 - - **Branch naming convention**: Use `github_username/description` format (e.g., `caidanw/feature-auth`, `caidanw/fix-database-timeout`) 175 - - **Base branch**: Always branch from `main` 176 - - **Create new branch**: `git checkout -b username/feature-name` 177 - 178 - ### Code Quality Standards 179 - 180 - #### Automated Checks 181 - 182 - Every commit automatically runs: 183 - 184 - 1. **Trailing whitespace removal** 185 - 2. **End-of-file fixing** 186 - 3. **YAML/JSON/TOML validation** 187 - 4. **Ruff linting and formatting** 188 - 189 - #### Manual Checks 190 - 191 - Before pushing, run: 192 - 193 - ```bash 194 - # Comprehensive linting check 195 - uv run ruff check 196 - 197 - # Format all code 198 - uv run ruff format 199 - 200 - # Type checking (on specific files/modules) 201 - uv run mypy osprey_worker/src/osprey_worker/lib 202 - # Or you can type check every module (this will happen in CI) 203 - uv run mypy . 204 - 205 - # Run all pre-commit hooks 206 - uv run pre-commit run --all-files 207 - ``` 208 - 209 - ### Commit Standards 210 - 211 - Follow [Conventional Commits](https://www.conventionalcommits.org/) format: 212 - 213 - ``` 214 - feat: add user authentication system 215 - fix: resolve database connection timeout 216 - docs: update API documentation 217 - refactor: simplify rule evaluation logic 218 - ``` 219 - 220 - **Examples:** 221 - 222 - - `feat:` - New features 223 - - `fix:` - Bug fixes 224 - - `docs:` - Documentation changes 225 - - `refactor:` - Code refactoring 226 - - `test:` - Adding or updating tests 227 - - `chore:` - Maintenance tasks 228 - 229 - ### Making Changes 230 - 231 - 1. **Create a new branch:** 232 - 233 - ```bash 234 - git checkout -b username/feature-name 235 - ``` 236 - 237 - 2. **Make your changes** 238 - 239 - 3. **Run quality checks:** 240 - 241 - ```bash 242 - uv run ruff check --fix 243 - uv run ruff format 244 - ``` 245 - 246 - 4. **Test your changes** (if tests exist) 247 - 248 - 5. **Commit your changes:** 249 - 250 - ```bash 251 - git add . 252 - git commit -m "feat: descriptive commit message" 253 - ``` 254 - 255 - Pre-commit hooks will run automatically and may fix formatting issues. 256 - 257 - 6. **Push your branch:** 258 - 259 - ```bash 260 - git push origin username/feature-name 261 - ``` 262 - 263 - ## Development Tools Overview 264 - 265 - ### Ruff - Linting and Formatting 266 - 267 - **Purpose**: Replaces Black, isort, Flake8, and other tools 268 - 269 - **Configuration**: Located in `pyproject.toml` under `[tool.ruff]` 270 - 271 - **Key Rules Enabled**: 272 - 273 - - `E` - pycodestyle errors 274 - - `F` - pyflakes 275 - - `I` - isort (import sorting) 276 - - `B006` - flake8-bugbear (mutable default arguments) 277 - 278 - **Commands**: 279 - 280 - ```bash 281 - # Check for issues 282 - uv run ruff check 283 - 284 - # Fix auto-fixable issues 285 - uv run ruff check --fix 286 - 287 - # Format code 288 - uv run ruff format 289 - 290 - # Check specific files 291 - uv run ruff check path/to/file.py 292 - ``` 293 - 294 - ### MyPy - Type Checking 295 - 296 - **Purpose**: Static type checking for Python 297 - 298 - **Configuration**: Located in `pyproject.toml` under `[tool.mypy]` 299 - 300 - **Key Features**: 301 - 302 - - Pydantic plugin support 303 - - SQLAlchemy plugin support 304 - - Relaxed strict mode (matching legacy codebase) 305 - - Ignores protobuf generated files 306 - 307 - **Commands**: 308 - 309 - ```bash 310 - # Type check entire project 311 - uv run mypy . 312 - 313 - # Type check specific files 314 - uv run mypy path/to/file.py 315 - 316 - # Type check entire module 317 - uv run mypy osprey_worker/ 318 - 319 - # Check with verbose output 320 - uv run mypy --show-traceback path/to/file.py 321 - ``` 322 - 323 - ### Pre-commit - Git Hooks 324 - 325 - **Purpose**: Automated quality checks before commits 326 - 327 - **Configuration**: Located in `.pre-commit-config.yaml` 328 - 329 - **Commands**: 330 - 331 - ```bash 332 - # Run all hooks on staged files 333 - uv run pre-commit run 334 - 335 - # Run all hooks on all files 336 - uv run pre-commit run --all-files 337 - 338 - # Run specific hook 339 - uv run pre-commit run ruff 340 - 341 - # Update hook versions 342 - uv run pre-commit autoupgrade 343 - 344 - # Bypass hooks (emergency only) 345 - git commit --no-verify 346 - ``` 347 - 348 - ### UV - Package Management 349 - 350 - **Purpose**: Fast Python package manager and environment management 351 - 352 - **Key Commands**: 353 - 354 - ```bash 355 - # Install dependencies 356 - uv sync 357 - 358 - # Add new dependency 359 - uv add package-name 360 - 361 - # Add development dependency 362 - uv add --group dev package-name 363 - 364 - # Remove dependency 365 - uv remove package-name 366 - 367 - # Run command in environment 368 - uv run command-name 369 - 370 - # Update dependencies 371 - uv lock --upgrade 372 - ``` 373 - 374 - ## Troubleshooting 375 - 376 - ### Common Issues 377 - 378 - #### "uv: command not found" 379 - 380 - **Solution**: Install uv using the installation script or pip: 381 - 382 - ```bash 383 - curl -LsSf https://astral.sh/uv/install.sh | sh 384 - # Then restart your terminal 385 - ``` 386 - 387 - #### Pre-commit hooks failing 388 - 389 - **Solution**: Run hooks manually to see detailed errors: 390 - 391 - ```bash 392 - uv run pre-commit run --all-files 393 - ``` 394 - 395 - #### MyPy errors on protobuf files 396 - 397 - **Solution**: Protobuf generated files are excluded in configuration. If you see errors, check that files match the exclusion patterns in `pyproject.toml`. 398 - 399 - #### Import errors during type checking 400 - 401 - **Solution**: Ensure all dependencies are installed: 402 - 403 - ```bash 404 - uv sync 405 - ``` 406 - 407 - ### Getting Help 408 - 409 - 1. **Check this documentation** for common setup issues 410 - 2. **Review error messages** carefully - they often contain solutions 411 - 3. **Run commands with verbose flags** for more detailed output 412 - 4. **Check configuration files** (`pyproject.toml`, `.pre-commit-config.yaml`) 413 - 414 - ## Next Steps 415 - 416 - - Check the [contributing guidelines](https://github.com/roostorg/.github/blob/main/CONTRIBUTING.md) for project-specific rules 417 - - Explore the codebase structure in `osprey_worker/`, `osprey_common/`, and `osprey_rpc/` 418 - 419 - ## IDE Setup Recommendations 420 - 421 - ### VS Code 422 - 423 - Install these extensions for the best development experience: 424 - 425 - - **Python** (Microsoft) 426 - - **Ruff** (Astral Software) 427 - - **MyPy Type Checker** (Microsoft) 428 - 429 - **Settings** (add to `.vscode/settings.json`): 430 - 431 - ```json 432 - { 433 - "python.defaultInterpreterPath": ".venv/bin/python", 434 - "ruff.enable": true, 435 - "ruff.organizeImports": true, 436 - "python.analysis.typeCheckingMode": "basic" 437 - } 438 - ``` 439 - 440 - This completes the development setup! You're now ready to contribute to Osprey.
+8
docs/README.md
··· 11 11 3. **[Development Guide](DEVELOPMENT.md)** - Set up your development environment and get started using Osprey 12 12 4. **[Osprey UI](UI.md)** - Understand how to use and navigate the UI 13 13 14 + ## Reporting a Bug or Issue 15 + 16 + Found a bug or have a feature request? We'd love to hear from you! When opening an issue, please use our templates: 17 + 18 + * [Bug Report](https://github.com/roostorg/osprey/issues/new?template=bug_report.md) 19 + * [Feature Request](https://github.com/roostorg/osprey/issues/new?template=feature_request.md) 20 + * [Submit an Egg (new tool idea) to ROOST!](https://github.com/roostorg/osprey/issues/new?template=documentation.md) 21 + 14 22 ## Need Help? 15 23 16 24 If you can't find what you're looking for in these documents, please:
+16
docs/SUMMARY.md
··· 1 + # Summary 2 + 3 + [Getting Started](README.md) 4 + 5 + - [Development](DEVELOPMENT.md) 6 + - [Workflow](development/workflow.md) 7 + - [Tools Overview](development/tools.md) 8 + - [Troubleshooting](development/troubleshooting.md) 9 + - [IDE Setup](development/ide.md) 10 + - [User Interface](UI.md) 11 + - [Writing Rules](rules.md) 12 + - [User Research & Personas](user_personas.md) 13 + 14 + --- 15 + 16 + [Contribute to these docs](docs.md)
+43 -62
docs/UI.md
··· 1 - # **Osprey User Interface Guide** 2 - 1 + # Osprey User Interface Guide 3 2 4 3 ## Getting Started 5 4 ··· 11 10 12 11 The Osprey UI has several pages accessible by a left-hand menu: 13 12 14 - ![Left Side Menu](../images/left-side-menu.png) 15 - 13 + ![Left Side Menu](images/left-side-menu.png) 16 14 17 15 Home will bring you to the default page of Osprey, with three main columns. 18 16 19 - **NOTE: The Event Stream in the right column is not yet in v0, and will be available before or in v1.** 20 - 21 - ![Osprey Home](../images/osprey-home.png) 17 + ![Osprey Home](images/osprey-home.png) 22 18 23 19 ### Left Column: Query 24 20 25 - #### **Query Box** 21 + #### Query Box 26 22 27 23 The Osprey Query UI uses the same SML syntax as rules, but for searching and filtering near-real-time and historical data rather than creating new rules. Using the test data generator, you can try writing a query to look for an action called “create\_post” specifically from a given User ID. 28 - ![Query Box](../images/query-box.png) 29 24 25 + ![Query Box](images/query-box.png) 30 26 31 27 You can also use a UDF in your query. If you ever forget what a UDF does, you can hover on the information symbol for a tip: 32 - ![Query UDF Hover](../images/query-udf-hover.png) 28 + 29 + ![Query UDF Hover](images/query-udf-hover.png) 33 30 34 31 A query can be run against a time window ranging from the last second to the last 3 months (and also a custom range): 35 - ![Query Time Range](../images/query-time-range.png) 36 32 33 + ![Query Time Range](images/query-time-range.png) 37 34 38 35 The Osprey UI is designed to be dynamic and update in real-time. If any other component in the other two columns is interacted with, the query will automatically update and vice versa. The query also automatically populates the URL. This can be handy for sharing a specific query with someone on a team, but may present privacy risks. 39 36 40 - ![Query and Charts](../images/query-and-charts.png) 37 + ![Query and Charts](images/query-and-charts.png) 41 38 42 - #### **History** 39 + #### History 43 40 44 41 Every query is logged in the Query History view, and there is a dropdown filter to only show queries that you have run. 45 42 46 43 When you hover over the query, it will also show the Top N Charts used during the query session (more on that below). 47 44 48 - ![Query History](../images/query-history.png) 45 + ![Query History](images/query-history.png) 49 46 50 47 The Query History can also be accessed and seen in a different format via the left-side menu. From here you can filter by the user who ran the query, view the original query, and run it using the same time range the original query used. 51 48 52 - ![Query History Page](../images/query-history-page.png) 49 + ![Query History Page](images/query-history-page.png) 53 50 54 - #### **Saved Queries** 51 + #### Saved Queries 55 52 56 53 If there are specific queries that are used often, Osprey provides the ability to save a query: 57 54 58 - ![Save Query History](../images/query-history-save.png) 55 + ![Save Query History](images/query-history-save.png) 59 56 60 57 The user who initiated the query and when the query was first run is logged as part of the Saved Query. Saved Queries can also be accessed via the left-side menu. The user who saved the query and what time it was saved is logged and visible. There is a drop-down menu at the top to filter saved queries by users. 61 58 62 - ![Saved Queries Page](../images/saved-queries-page.png) 59 + ![Saved Queries Page](images/saved-queries-page.png) 63 60 64 61 ### Middle Column: Charts 65 62 66 63 The middle column in Osprey shows two types of charts: **Time Series** and **Top N Results**. Both sections provide the ability to add extra charts to see different slices of time or types of top results. 67 64 68 - ![Charts](../images/charts.png) 65 + ![Charts](images/charts.png) 69 66 70 - #### **Time Series Chart** 67 + #### Time Series Chart 71 68 72 69 The Time Series chart shows a visualization of the results in the query over a period of time. The time ranges include: 73 70 ··· 80 77 * Month 81 78 82 79 Hovering over a bar in the time series chart shows how many events took place during that time. 83 - ![Time Series](../images/hover-time-series.png) 84 80 81 + ![Time Series](images/hover-time-series.png) 85 82 86 83 There is also a time and date picker above the time series chart where you can set a custom range: 87 - ![Date Picker](../images/time-date-picker.png) 84 + 85 + ![Date Picker](images/time-date-picker.png) 88 86 89 87 An extra table can be added for another view of a different unit of time. To get rid of the table, you can “[yeet](https://www.urbandictionary.com/define.php?term=Yeet) it”. 90 - ![Multiple Time Series](../images/multiple-time-series.png) 91 88 89 + ![Multiple Time Series](images/multiple-time-series.png) 92 90 93 - #### **Top N Results** 91 + #### Top N Results 94 92 95 93 Adding a Top N Results table populates a table with the top results for the results of the query. You can view and assign labels to a specific entity by hovering over it and clicking “Edit Labels” 96 - ![Top N Charts](../images/top-n-charts.png) 97 94 98 - **NOTE: Labels are not yet in v0** 95 + ![Top N Charts](images/top-n-charts.png) 99 96 100 - ![Add Labels](../images/add-labels.png) 97 + ![Add Labels](images/add-labels.png) 101 98 102 99 You can also select PoP (Period over Period) to compare the query results with results from a window of time in the past to see the delta. 103 100 104 - ![Period Over Period](../images/pop.png) 101 + ![Period Over Period](images/pop.png) 105 102 106 103 ### Right Column: Event Stream 107 104 108 - **The Event Stream is not yet in v0, and will be available before or in v1.** 109 - 110 105 The Event Stream is essentially Osprey's "live feed" and investigation dashboard where security teams can: 111 106 112 107 * Monitor real-time activity ··· 116 111 * Drill down into specific users/entities 117 112 118 113 It provides a more detailed view of each event that matches the query. The Event Stream can show metadata related to accounts that can link to other internal tools that provide detailed information about an account and/or further enforcement actions. 119 - ![Event Stream](../images/event-stream.png) 120 - 114 + ![Event Stream](images/event-stream.png) 121 115 122 116 The event stream is also viewable in a card format vs a list format (list format shown in the screenshot). 123 117 124 118 Osprey users may have personal preferences on how to do investigations and what information is most helpful for them. Osprey makes it easy to customize the types of information shown in the Event Stream by clicking “Summary Features” 125 - ![Summary Features](../images/summary-features.png) 126 - 119 + ![Summary Features](images/summary-features.png) 127 120 128 121 ### Labeling 129 122 130 - **Note: Labels are not yet in v0, but will come in v1** 131 123 Any unique entity can be labeled in the Osprey UI. This manual labeling tool is used by Safety teams to tag individual entities (users, IPs, emails, etc.) with labels. Labels are essentially the manual annotation tool that feeds into Osprey's automated rule system, allowing human judgment to enhance machine detection. Labels can be positive, negative, or neutral. Examples: 132 124 133 125 **Negative Labels: Harmful/problematic behavior** ··· 139 131 **Neutral Labels: Informational tags** 140 132 * Examples: "new\_user", "from\_mobile", "beta\_tester" 141 133 142 - 143 - Below are examples of a new label interface from v0, and an example from Discord’s usage of labels (coming in v1). 144 - ![Empty Label](../images/empty-label.png) 145 - ![Complete Label](../images/complete-label.png) 146 - 134 + ![Empty Label](images/empty-label.png) 135 + ![Complete Label](images/complete-label.png) 147 136 148 137 ### UDF Documentation 149 138 150 139 The UDF Documentation page can be accessed via the left-side menu. It dynamically updates based on the code, so any new UDFs added will show up on this page. This page essentially serves as the "API reference" for the SML language, making it easy for users to discover and properly use all available functions when writing rules and queries. 151 - ![UDF Documentation](../images/udf-documentation.png) 140 + ![UDF Documentation](images/udf-documentation.png) 152 141 153 142 This page can be used as a manual for writing SML rules or queries, guide for understanding parameter types and requirements, and act as a plugin discovery portal to explore what custom UDFs are loaded. 154 143 155 144 ### Bulk Labeling 156 - 157 - **Note: Since Bulk Labeling relies on Labels, it does not yet work in v0.** 158 145 159 146 There are two ways to bulk label items in Osprey: the left-side menu and via the chart column. In this example, you can bulk label all the users that have posted a message that is not empty: 160 - ![Bulk Label](../images/bulk-label.png) 161 147 148 + ![Bulk Label](images/bulk-label.png) 162 149 163 150 **Bulk labels can be dangerous if there’s a false positive\!** Osprey provides a counter of how many unique entities are about to be bulk labeled at the top. Labels can be positive, negative, or neutral. A reason must be provided when labeling anything. Each bulk job will create a unique task ID and log the user who initiated the bulk job, the status of the bulk labeling, and a link to the query that the bulk job originated from. 164 151 165 152 To view all bulk labeling jobs that have been done, click into “Bulk Job History” from the left-side menu. You’ll need the unique task ID to look up a bulk job. 166 - ![Bulk Job History](../images/bulk-job-history.png) 167 153 154 + ![Bulk Job History](images/bulk-job-history.png) 168 155 169 156 ### Rule Visualizer 170 - 171 - **Note: Since the Rule Visualizer relies on Labels, it does not yet work in v0.** 172 157 173 158 The Rule Visualizer shows how upstream labels, rules, and downstream labels interact with one another. To use it, select an Action or a Label. A graph view will appear showing the relations between rules and labels. 174 159 ··· 176 161 * **Blue square:** rule 177 162 * **Green circle:** label downstream of a rule 178 163 179 - ![Rule Visualizer](../images/rules-visualizer.png) 180 - 164 + ![Rule Visualizer](images/rules-visualizer.png) 181 165 182 166 ### Query Syntax 183 167 184 - #### **Actions** 168 + #### Actions 185 169 186 170 Actions are events that are sent to Osprey. An event is simply something that happens. When a user does something like create a post, send a message, change their username, etc an event happens to represent that. There are probably a lot of events emitted in your org, and Osprey doesn’t need to consume all of them. 187 171 ··· 212 196 * Post Text 213 197 * Internet Service Provider 214 198 215 - #### **Effects** 199 + #### Effects 216 200 217 201 Effects can be triggered when one or many rules are evaluated to be true. These are validated and handled in aggregate at the end of an execution output. For example, an effect might apply a label to an entity marking it as a “Spammer”. 218 202 ··· 227 211 MessageText != Null 228 212 ``` 229 213 230 - #### **Combining Conditions** 214 + #### Combining Conditions 231 215 232 216 Let’s say you’re looking for any matches where a user tried to login more than 3 times. You can create a query to check for two types of data fields: “EventType” and “LoginAttempts”. 233 217 ··· 242 226 (UserId == 123) or (UserId == 456) 243 227 ``` 244 228 245 - #### **Using UDFs in Queries** 229 + #### Using UDFs in Queries 246 230 247 - UDFs (read more [here](https://github.com/roostorg/osprey/blob/f16da6e5c32ae124c3cc6e2d7efded7cea1ac726/docs/rules.md#user-defined-functions-udfs)) are a powerful part of queries. Once you define a UDF with the specific desired logic, you can reference it in a query. 231 + UDFs (read more [here](rules.md#user-defined-functions-udfs)) are a powerful part of queries. Once you define a UDF with the specific desired logic, you can reference it in a query. 248 232 249 - **NOTE: If you try to query a UDF that doesn’t exist, Osprey will silently fail with a 500 error.** 233 + > [!NOTE] 234 + > If you try to query a UDF that doesn’t exist, Osprey will silently fail with a 500 error. 250 235 251 236 ```py 252 237 # Text search ··· 257 242 ListLength(list=UserConnections) > 10 258 243 ``` 259 244 260 - #### **Label Queries** 261 - 262 - **Important Note: Labels are not yet in v0, so these will not work in the UI.** 245 + #### Label Queries 263 246 264 247 Since the UI searches across actions/events: 265 248 266 - * **Don't use:** HasLabel() \- won't work in Query UI 267 - * **Use instead**: DidAddLabel() \- shows when an action added a label 249 + * **Don't use:** HasLabel() - won't work in Query UI 250 + * **Use instead**: DidAddLabel() - shows when an action added a label 268 251 269 252 ```py 270 253 # Find actions that added specific labels 271 254 DidAddLabel(entity_type="UserId", label_name="likely_spammer") 272 255 DidAddLabel(entity_type="IpAddress", label_name="suspicious") 273 256 ``` 274 - 275 - ### 276 257 277 258 ### Example Queries 278 259
+20
docs/development/ide.md
··· 1 + # IDE Setup Recommendations 2 + 3 + ## VS Code 4 + 5 + Install these extensions for the best development experience: 6 + 7 + - **Python** (Microsoft) 8 + - **Ruff** (Astral Software) 9 + - **MyPy Type Checker** (Microsoft) 10 + 11 + **Settings** (add to `.vscode/settings.json`): 12 + 13 + ```json 14 + { 15 + "python.defaultInterpreterPath": ".venv/bin/python", 16 + "ruff.enable": true, 17 + "ruff.organizeImports": true, 18 + "python.analysis.typeCheckingMode": "basic" 19 + } 20 + ```
+110
docs/development/tools.md
··· 1 + # Development Tools Overview 2 + 3 + ## Ruff - Linting and Formatting 4 + 5 + **Purpose**: Replaces Black, isort, Flake8, and other tools 6 + 7 + **Configuration**: Located in `pyproject.toml` under `[tool.ruff]` 8 + 9 + **Key Rules Enabled**: 10 + 11 + - `E` - pycodestyle errors 12 + - `F` - pyflakes 13 + - `I` - isort (import sorting) 14 + - `B006` - flake8-bugbear (mutable default arguments) 15 + 16 + **Commands**: 17 + 18 + ```bash 19 + # Check for issues 20 + uv run ruff check 21 + 22 + # Fix auto-fixable issues 23 + uv run ruff check --fix 24 + 25 + # Format code 26 + uv run ruff format 27 + 28 + # Check specific files 29 + uv run ruff check path/to/file.py 30 + ``` 31 + 32 + ## MyPy - Type Checking 33 + 34 + **Purpose**: Static type checking for Python 35 + 36 + **Configuration**: Located in `pyproject.toml` under `[tool.mypy]` 37 + 38 + **Key Features**: 39 + 40 + - Pydantic plugin support 41 + - SQLAlchemy plugin support 42 + - Relaxed strict mode (matching legacy codebase) 43 + - Ignores protobuf generated files 44 + 45 + **Commands**: 46 + 47 + ```bash 48 + # Type check entire project 49 + uv run mypy . 50 + 51 + # Type check specific files 52 + uv run mypy path/to/file.py 53 + 54 + # Type check entire module 55 + uv run mypy osprey_worker/ 56 + 57 + # Check with verbose output 58 + uv run mypy --show-traceback path/to/file.py 59 + ``` 60 + 61 + ## Pre-commit - Git Hooks 62 + 63 + **Purpose**: Automated quality checks before commits 64 + 65 + **Configuration**: Located in `.pre-commit-config.yaml` 66 + 67 + **Commands**: 68 + 69 + ```bash 70 + # Run all hooks on staged files 71 + uv run pre-commit run 72 + 73 + # Run all hooks on all files 74 + uv run pre-commit run --all-files 75 + 76 + # Run specific hook 77 + uv run pre-commit run ruff 78 + 79 + # Update hook versions 80 + uv run pre-commit autoupgrade 81 + 82 + # Bypass hooks (emergency only) 83 + git commit --no-verify 84 + ``` 85 + 86 + ## UV - Package Management 87 + 88 + **Purpose**: Fast Python package manager and environment management 89 + 90 + **Key Commands**: 91 + 92 + ```bash 93 + # Install dependencies 94 + uv sync 95 + 96 + # Add new dependency 97 + uv add package-name 98 + 99 + # Add development dependency 100 + uv add --group dev package-name 101 + 102 + # Remove dependency 103 + uv remove package-name 104 + 105 + # Run command in environment 106 + uv run command-name 107 + 108 + # Update dependencies 109 + uv lock --upgrade 110 + ```
+32
docs/development/troubleshooting.md
··· 1 + # Troubleshooting 2 + 3 + ## Common Issues 4 + 5 + ### "uv: command not found" 6 + 7 + **Solution**: Install uv using the installation script or pip: 8 + 9 + ```bash 10 + curl -LsSf https://astral.sh/uv/install.sh | sh 11 + # Then restart your terminal 12 + ``` 13 + 14 + ### Pre-commit hooks failing 15 + 16 + **Solution**: Run hooks manually to see detailed errors: 17 + 18 + ```bash 19 + uv run pre-commit run --all-files 20 + ``` 21 + 22 + ### MyPy errors on protobuf files 23 + 24 + **Solution**: Protobuf generated files are excluded in configuration. If you see errors, check that files match the exclusion patterns in `pyproject.toml`. 25 + 26 + ### Import errors during type checking 27 + 28 + **Solution**: Ensure all dependencies are installed: 29 + 30 + ```bash 31 + uv sync 32 + ```
+92
docs/development/workflow.md
··· 1 + # Development Workflow 2 + 3 + ## Branch Management 4 + 5 + - **Branch naming convention**: Use `github_username/description` format (e.g., `caidanw/feature-auth`, `caidanw/fix-database-timeout`) 6 + - **Base branch**: Always branch from `main` 7 + - **Create new branch**: `git checkout -b username/feature-name` 8 + 9 + ## Code Quality Standards 10 + 11 + ### Automated Checks 12 + 13 + Every commit automatically runs: 14 + 15 + 1. **Trailing whitespace removal** 16 + 2. **End-of-file fixing** 17 + 3. **YAML/JSON/TOML validation** 18 + 4. **Ruff linting and formatting** 19 + 20 + ### Manual Checks 21 + 22 + Before pushing, run: 23 + 24 + ```bash 25 + # Comprehensive linting check 26 + uv run ruff check 27 + 28 + # Format all code 29 + uv run ruff format 30 + 31 + # Type checking (on specific files/modules) 32 + uv run mypy osprey_worker/src/osprey_worker/lib 33 + # Or you can type check every module (this will happen in CI) 34 + uv run mypy . 35 + 36 + # Run all pre-commit hooks 37 + uv run pre-commit run --all-files 38 + ``` 39 + 40 + ## Commit Standards 41 + 42 + Follow [Conventional Commits](https://www.conventionalcommits.org/) format: 43 + 44 + ``` 45 + feat: add user authentication system 46 + fix: resolve database connection timeout 47 + docs: update API documentation 48 + refactor: simplify rule evaluation logic 49 + ``` 50 + 51 + **Examples:** 52 + 53 + - `feat:` - New features 54 + - `fix:` - Bug fixes 55 + - `docs:` - Documentation changes 56 + - `refactor:` - Code refactoring 57 + - `test:` - Adding or updating tests 58 + - `chore:` - Maintenance tasks 59 + 60 + ## Making Changes 61 + 62 + 1. **Create a new branch:** 63 + 64 + ```bash 65 + git checkout -b username/feature-name 66 + ``` 67 + 68 + 2. **Make your changes** 69 + 70 + 3. **Run quality checks:** 71 + 72 + ```bash 73 + uv run ruff check --fix 74 + uv run ruff format 75 + ``` 76 + 77 + 4. **Test your changes** (if tests exist) 78 + 79 + 5. **Commit your changes:** 80 + 81 + ```bash 82 + git add . 83 + git commit -m "feat: descriptive commit message" 84 + ``` 85 + 86 + Pre-commit hooks will run automatically and may fix formatting issues. 87 + 88 + 6. **Push your branch:** 89 + 90 + ```bash 91 + git push origin username/feature-name 92 + ```
+20
docs/docs.md
··· 1 + # Contribute to these docs 2 + 3 + This documentation site is built using [mdBook](https://rust-lang.github.io/mdBook/) and deployed to GitHub Pages. Changes merged into the `main` branch will automatically be built and deployed. 4 + 5 + Documentation can be edited directly in the GitHub web UI for existing pages, or by selecting the **🖉 Suggest an edit** icon at the top of the docs site. To create a new page, be sure to update `SUMMARY.md` as well. Once you're done with your changes, open a pull request for review. 6 + 7 + To understand more about how mdBook works, learn about the [anatomy of a book](https://rust-lang.github.io/mdBook/guide/creating.html#anatomy-of-a-book). 8 + 9 + ## Developing locally 10 + 11 + To build the site locally, clone this repository and install `mdbook` (follow the [official installation instructions](https://rust-lang.github.io/mdBook/guide/installation.html)). 12 + 13 + Once installed, use the `mdbook` command-line tool from the root of this repo. For example, to automatically start watching, building, and serving the site: 14 + 15 + ```shell 16 + mdbook serve 17 + ``` 18 + 19 + Then make your changes, preview them in your web browser (at [http://localhost:3000](http://localhost:3000) by default), commit, push, and open a pull request like any other git project. 20 +
+3 -8
docs/rules.md
··· 1 - # Osprey Docs 2 - 3 - # Osprey Docs 1 + # Osprey Rules 4 2 5 3 ![images/rules_architecture.png](images/rules_architecture.png) 6 4 7 - ## Rules 8 - 9 - ### Creating Rules 5 + ## Creating Rules 10 6 11 7 Rules in Osprey are written in `Some Madeup Language (SML)` and follow most syntax conventions present in the Osprey Query UI. SML is a subset of Python with additional restrictions to make the rules simpler to craft. 12 8 ··· 58 54 ) 59 55 ``` 60 56 61 - ### Instrumenting Rules with WhenRules 57 + ## Instrumenting Rules with WhenRules 62 58 63 59 The `WhenRules()` function allows for the connection of rules with external services, declarations or internal label modifications by listing Rule objects in sequence within the `rules_any=[]` parameter and `EffectBase`. By default, operators and designers can utilize UDFs with predefined effects such as `DeclareVerdict()`, `LabelAdd()`, and `LabelRemove()` on positive rule evaluations. 64 60 ··· 179 175 UDF outputs can also implement the `CustomExtractedFeature` interface - which get persisted in the outputs for the UI. `EffectToCustomExtractedFeatureBase` can also be used when effects need additional processing for use in the UI. 180 176 181 177 ## Labels 182 - **NOTE: Labels are currently not in v0, so users will be unable to add or edit labels via the UI** 183 178 184 179 Labels are a standard plugin that enable stateful rules, and touch many parts of Osprey. They are effectively tags on various entities, which can be arbitrarily defined. 185 180
images/Rules_Engine_Overview.jpeg docs/images/Rules_Engine_Overview.jpeg
images/add-labels.png docs/images/add-labels.png
images/bulk-job-history.png docs/images/bulk-job-history.png
images/bulk-label.png docs/images/bulk-label.png
images/charts.png docs/images/charts.png
images/complete-label.png docs/images/complete-label.png
images/empty-label.png docs/images/empty-label.png
images/event-stream.png docs/images/event-stream.png
images/hover-query-history.png docs/images/hover-query-history.png
images/hover-time-series.png docs/images/hover-time-series.png
images/left-side-menu.png docs/images/left-side-menu.png
images/multiple-time-series.png docs/images/multiple-time-series.png
images/multiple-top-charts.png docs/images/multiple-top-charts.png
images/osprey-home.png docs/images/osprey-home.png
images/pop.png docs/images/pop.png
images/query-and-charts.png docs/images/query-and-charts.png
images/query-box.png docs/images/query-box.png
images/query-history-page.png docs/images/query-history-page.png
images/query-history-save.png docs/images/query-history-save.png
images/query-history-search.png docs/images/query-history-search.png
images/query-history.png docs/images/query-history.png
images/query-time-range.png docs/images/query-time-range.png
images/query-udf-hover.png docs/images/query-udf-hover.png
images/rules-visualizer.png docs/images/rules-visualizer.png
images/saved-queries-page.png docs/images/saved-queries-page.png
images/summary-features.png docs/images/summary-features.png
images/time-date-picker.png docs/images/time-date-picker.png
images/top-n-charts.png docs/images/top-n-charts.png
images/udf-documentation.png docs/images/udf-documentation.png