README.md

# FYI

**Know what's happening in your app.** In-app events, user feedback, and instant Slack/Telegram notifications for Phoenix.

Stop refreshing your database to see if users are signing up. FYI gives you:

- 📤 **Event tracking** — Emit events from anywhere in your app with one line of code
- 📊 **Live dashboard** — Beautiful admin UI with search, filtering, and activity histograms
- 💬 **Feedback widget** — Drop-in component to collect user feedback (installs into your codebase)
- 🔔 **Instant notifications** — Get pinged in Slack or Telegram when important things happen
- 🎯 **Smart routing** — Send specific events to specific channels with glob patterns
- 🚀 **One-command setup** — `mix fyi.install` handles migrations, config, and routes

![FYI Admin Inbox](screenshot.png)

## Installation

Add `fyi` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:fyi, "~> 0.1.0"}
  ]
end
```

Then run the installer:

```bash
mix deps.get
mix fyi.install
```

This will:
1. Add `FYI.Application` to your supervision tree
2. Create a migration for the `fyi_events` table
3. Print instructions to add the `/fyi` route to your router
4. Add configuration stubs to your config files

### Installer Options

- `--no-ui` — Skip installing the admin inbox UI
- `--no-persist` — Skip the database migration (events won't be persisted)
- `--no-feedback` — Skip installing the feedback component

## Configuration

```elixir
# config/config.exs
config :fyi,
  app_name: "MyApp",
  persist_events: true,
  repo: MyApp.Repo,
  sinks: [
    {FYI.Sink.SlackWebhook, %{url: System.get_env("SLACK_WEBHOOK_URL")}},
    {FYI.Sink.Telegram, %{
      token: System.get_env("TELEGRAM_BOT_TOKEN"),
      chat_id: System.get_env("TELEGRAM_CHAT_ID")
    }}
  ],
  routes: [
    %{match: "waitlist.*", sinks: [:slack]},
    %{match: "purchase.*", sinks: [:slack, :telegram]},
    %{match: "feedback.*", sinks: [:slack]}
  ]
```

### App Name

Set `app_name` to identify events when multiple apps share the same Slack channel or Telegram chat:

```elixir
config :fyi, app_name: "MyApp"
```

Messages will include the app name: `[MyApp] *purchase.created* by user_123`

### Emojis

Add emojis to your notifications in three ways (in priority order):

**1. Per-event override:**
```elixir
FYI.emit("error.critical", %{message: "DB down"}, emoji: "🚨")
```

**2. Pattern-based mapping:**
```elixir
config :fyi,
  emojis: %{
    "purchase.*" => "💰",
    "user.signup" => "👋",
    "feedback.*" => "💬",
    "error.*" => "🚨"
  }
```

**3. Default fallback:**
```elixir
config :fyi, emoji: "📣"
```

Messages will show as: `💰 [MyApp] *purchase.created* by user_123`

### Routing

Routes use simple glob matching:
- `purchase.*` matches `purchase.created`, `purchase.updated`, etc.
- `*` at the end matches any suffix

If no routes are configured, all events go to all sinks.

## Usage

### Emit an Event

```elixir
FYI.emit("purchase.created", %{amount: 4900, currency: "GBP"}, actor: user_id)

FYI.emit("user.signup", %{email: "user@example.com"}, source: "landing_page")

FYI.emit("error.critical", %{message: "DB connection failed"}, emoji: "🚨", tags: %{env: "prod"})
```

Options:
- `:actor` - who triggered the event (user_id, email, etc.)
- `:source` - where the event originated (e.g., "api", "web", "worker")
- `:tags` - additional metadata map for filtering
- `:emoji` - override emoji for this specific event

### Emit from Ecto.Multi (Recommended)

```elixir
Ecto.Multi.new()
|> Ecto.Multi.insert(:purchase, changeset)
|> FYI.Multi.emit("purchase.created", fn %{purchase: p} ->
  %{payload: %{amount: p.amount, currency: p.currency}, actor: p.user_id}
end)
|> Repo.transaction()
```

This ensures events are only emitted after the transaction commits successfully.

### Feedback Component

The installer creates a customizable feedback component in your codebase at `lib/your_app_web/components/fyi/feedback_component.ex`.

Use it in any LiveView:

```elixir
import MyAppWeb.FYI.FeedbackComponent

# In your template
<.feedback_button />
```

Customize as needed:

```heex
<.feedback_button
  title="Report an Issue"
  button_label="Report"
  button_icon="🐛"
  categories={[{"bug", "Bug"}, {"ux", "UX Problem"}, {"other", "Other"}]}
/>
```

Since the component lives in your codebase, you can freely modify the Tailwind classes, add fields, or change the behavior.

Skip installing with `mix fyi.install --no-feedback`.

### Admin Inbox

Add the route to your router (the installer prints this):

```elixir
# In router.ex
scope "/fyi", FYI.Web do
  pipe_through [:browser]
  live "/", InboxLive, :index
  live "/events/:id", InboxLive, :show
end
```

Visit `/fyi` to see the event inbox with:
- Activity histogram with time-based tooltips
- Real-time event updates (requires PubSub config)
- Time range filtering (5 minutes to all time)
- Event type filtering
- Search by event name or actor
- Event detail panel with full payload

### Real-time Updates

To enable real-time updates in the admin inbox, add your PubSub module:

```elixir
config :fyi, pubsub: MyApp.PubSub
```

New events will appear instantly without refreshing the page.

## Built-in Sinks

### Slack Webhook

```elixir
{FYI.Sink.SlackWebhook, %{
  url: "https://hooks.slack.com/services/...",
  username: "FYI Bot",      # optional
  icon_emoji: ":bell:"      # optional
}}
```

### Telegram Bot

```elixir
{FYI.Sink.Telegram, %{
  token: "123456:ABC-DEF...",
  chat_id: "-1001234567890",
  parse_mode: "HTML"         # optional, default: "HTML"
}}
```

## Custom Sinks

Implement the `FYI.Sink` behaviour:

```elixir
defmodule MyApp.DiscordSink do
  @behaviour FYI.Sink

  @impl true
  def id, do: :discord

  @impl true
  def init(config) do
    {:ok, %{webhook_url: config.url}}
  end

  @impl true
  def deliver(event, state) do
    # POST to Discord webhook
    case Req.post(state.webhook_url, json: %{content: event.name}) do
      {:ok, %{status: s}} when s in 200..299 -> :ok
      {:ok, resp} -> {:error, resp}
      {:error, err} -> {:error, err}
    end
  end
end
```

Then add it to your config:

```elixir
sinks: [
  {MyApp.DiscordSink, %{url: "https://discord.com/api/webhooks/..."}}
]
```

## Design Philosophy

FYI is intentionally simple:

- ❌ No Oban
- ❌ No durable queues, retries, or backoff
- ✅ Fire-and-forget HTTP notifications
- ✅ Phoenix + Ecto assumed
- ✅ Failures are logged, never block

Think "Oban Pro install experience", but for events + feedback.

## Development

To use FYI locally without publishing to Hex:

```elixir
# In your app's mix.exs
{:fyi, path: "../fyi"}
```

## License

MIT