AuroraMeter. Components
(Aurora Meter v1.0.0)
View Source
Drop-in HEEx components for showing live usage and credit spend. Compiled
only when Phoenix.Component is available (the LiveView deps are optional).
They are unstyled by design (BEM-style aurora-meter__*, aurora-spend-chart__*
and aurora-credit-summary__* classes) so they inherit your app's look; the
Pro dashboard ships a styled version. Nothing here declares a colour: the
charts paint with currentColor, so they take the text colour your design
system already set. There is no JavaScript: the chart is inline SVG and the
tooltips are <title> elements. Pair with AuroraMeter.LiveView.subscribe/1
for live usage updates.
Money components render dollars through AuroraMeter.Credits.Money, never
raw micro-dollars:
<.spend_chart points={AuroraMeter.Credits.spend_history(@org, days: 30)} />
<.credit_summary summary={AuroraMeter.Credits.summary(@org)} />
Summary
Functions
Renders the credit balance and burn from AuroraMeter.Credits.summary/1 as a
description list.
Renders a spend-per-bucket bar chart from AuroraMeter.Credits.spend_history/2.
Renders a labelled usage bar for one feature.
Renders a usage meter for every feature in the tenant's plan.
Functions
Renders the credit balance and burn from AuroraMeter.Credits.summary/1 as a
description list.
Held, promotional and granted figures only appear when they are non-zero.
daily_burn and runway_days render as "not yet" when they are nil. There is
no honest number to show for a tenant that has not spent anything, and this
component will not invent one.
Examples
<.credit_summary summary={AuroraMeter.Credits.summary(@org)} />Attributes
summary(:map) (required)- Global attributes are accepted.
Renders a spend-per-bucket bar chart from AuroraMeter.Credits.spend_history/2.
Every bucket in points gets a bar, including the empty ones: a zero-spend
day is a baseline, never a gap. Buckets where credit was granted carry a
marker above the bar when show_grants is set. Amounts in the axis labels
and in the <title> tooltips are dollars, formatted with
AuroraMeter.Credits.Money.format/2.
Examples
<.spend_chart points={AuroraMeter.Credits.spend_history(@org, days: 30)} />
<.spend_chart
points={AuroraMeter.Credits.spend_history(@org, bucket: :month, days: 365)}
label="Spend per month"
height={160}
/>Attributes
points(:list) (required)height(:integer) - Defaults to120.label(:string) - Defaults to"Spend per day".show_grants(:boolean) - Defaults totrue.- Global attributes are accepted.
Renders a labelled usage bar for one feature.
Hard caps show used / limit; metered features show used / included and
flag overage; counters show the bare count with no bar (they have no
denominator, ADR 0006); boolean features show whether they are enabled;
integer features show their plan value.
Pass quota in a LiveView, tenant and feature anywhere else
# Live: updates when usage moves.
<.usage_meter quota={@quota} />
# Snapshot: reads the current value once, at render time.
<.usage_meter tenant={@org} feature={:ai_generations} />The two forms are not interchangeable inside a LiveView, and the reason
is change tracking rather than taste. LiveView re-renders a function
component only when the assigns passed to it have changed. The tenant
form's assigns are the tenant and the feature name, and neither of those
moves when usage does, so a socket that is subscribed correctly, receives
the broadcast and re-renders would keep showing the figure read at the
first render. That is not a bar that lags; it is a bar that never moves
again, and nothing on the page says so.
The quota form has no such gap, because the number is the assign:
when usage changes the map changes, and change tracking re-renders for the
ordinary reason. Fetch it with AuroraMeter.quota/2 in mount/3 and again
wherever you handle the usage message:
def mount(_params, session, socket) do
org = MyApp.Accounts.org_for_session!(session)
if connected?(socket), do: AuroraMeter.LiveView.subscribe(org)
{:ok, assign(socket, org: org, quota: AuroraMeter.quota(org, :ai_generations))}
end
def handle_info({:aurora_meter, :usage, _payload} = message, socket) do
{:noreply, AuroraMeter.LiveView.assign_quota(message, socket, :quota, :ai_generations)}
endAuroraMeter.LiveView.assign_quota/4 is that second line; it drops a
message for another tenant and re-reads the quota for the socket's own.
Exactly one of quota, or tenant and feature, is required. Passing
neither, or both, raises rather than guessing which one the caller meant.
Attributes
quota(:map) - A map fromAuroraMeter.quota/2. The live form: prefer it in a LiveView. Defaults tonil.tenant(:any) - Read the quota at render time. A snapshot. Defaults tonil.feature(:atom) - Required withtenant. Defaults tonil.label(:string) - Defaults tonil.- Global attributes are accepted.
Renders a usage meter for every feature in the tenant's plan.
quotas and tenant divide exactly as they do in usage_meter/1, for the
same change-tracking reason: pass quotas={@quotas} in a LiveView so the
meters move, tenant={@org} anywhere a single snapshot is what is wanted.
AuroraMeter.LiveView.quotas/1 builds the list and
AuroraMeter.LiveView.assign_quotas/2 refreshes it from a usage message.
<.usage_summary quotas={@quotas} />
<.usage_summary tenant={@org} />Attributes
quotas(:list) - A list of maps fromAuroraMeter.quota/2. The live form. Defaults tonil.tenant(:any) - Read every feature's quota at render time. Defaults tonil.- Global attributes are accepted.