feat: add type and outcome event dimensions - #30
Conversation
Two metric-scoped dimensions, so a metric can be broken down by what the
row is about and how the attempt ended without encoding either into the
metric name.
They replace the pattern where a category becomes part of the name and the
consumer parses it back out — auth.method.phone.{countryCode} is ~190
metric names for one concept, and messages.{type}.{provider}.{sent|failed}
is a cross-product of nine names built from three numbers.
Both are read with `metric` pinned, so the same column carries different
value spaces across metrics — a messaging channel for one, a calling code
for another. That is the shape resourceId already has, where a bucket id
and a function id share a column scoped by resourceType.
set(0) indexed like the other small closed value spaces, and
LowCardinality since each metric contributes only a handful of values.
Gauges are unchanged: GAUGE_COLUMNS does not include them, so the gauge
path still rejects both as unknown tags.
Greptile SummaryAdds metric-scoped
Confidence Score: 4/5The PR is not safe to merge until the new Database writes allow the new tag-derived Files Needing Attention: src/Usage/Metric.php and src/Usage/Adapter/Database.php Important Files Changed
Prompt To Fix All With AI### Issue 1
src/Usage/Metric.php:66
**Event type discriminator overwritten**
When a Database-backed event includes a metric-scoped `type` tag such as `type=sms`, extraction merges that value over the adapter's internal `type=event` discriminator, causing typed event queries and totals to omit the stored row.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "feat: add type and outcome event dimensi..." | Re-trigger Greptile |
| @@ -64,6 +64,8 @@ class Metric extends ArrayObject | |||
| // sdk identity | |||
| 'sdk', 'sdkVersion', | |||
| 'deviceName', 'deviceBrand', 'deviceModel', | |||
There was a problem hiding this comment.
Event type discriminator overwritten
When a Database-backed event includes a metric-scoped type tag such as type=sms, extraction merges that value over the adapter's internal type=event discriminator, causing typed event queries and totals to omit the stored row.
Knowledge Base Used:
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/Usage/Metric.php
Line: 66
Comment:
**Event type discriminator overwritten**
When a Database-backed event includes a metric-scoped `type` tag such as `type=sms`, extraction merges that value over the adapter's internal `type=event` discriminator, causing typed event queries and totals to omit the stored row.
**Knowledge Base Used:**
- [Usage domain model](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/utopia-php/usage/-/docs/usage-domain-model.md)
- [Usage storage adapters](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/utopia-php/usage/-/docs/usage-storage-adapters.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Adds two metric-scoped event dimensions:
typeandoutcome.Why
Today a category has to be encoded into the metric name and parsed back out by the consumer:
auth.method.phone.{countryCode}— ~190 metric names for one concept. Billing can't group by it, so it enumerates every calling code into anINlist.messages.{type}.{provider}[.sent|.failed]— a cross-product of nine names built from three numbers (recipients, delivered, failed), at three granularities.webhooks.events.{sent|failed}— the same outcome split again.With these two columns, the first becomes
GROUP BY metric, typeand the second collapses to two rows taggedtype+outcome.Metric-scoped by design
Both are read with
metricpinned, so the same column carries different value spaces across metrics — a messaging channel for one, a calling code for another. That's the shaperesourceIdalready has, where a bucket id and a function id share one column scoped byresourceType.Deliberately not reusing
status: it holds HTTP status, andsent/failedsort above400lexicographically, so an error filter (greaterThanEqual("status", "400")) would silently match every message row.Details
set(0)indexed, like the other small closed value spaces (status,method,service,clientType).setsupports ranges as well as equality, unlikebloom_filter.LowCardinality(Nullable(String))— each metric contributes a handful of values.type64 /outcome32.GAUGE_COLUMNSdoesn't include them, so the gauge path still rejects both as unknown tags — verified.setup()already emitsALTER TABLE … ADD COLUMN IF NOT EXISTS, so existing tables pick them up without a migration.Verification
Confirmed end to end, since
extractColumns()is strict (unknown tags throw rather than being dropped):And the query shapes the consumers need actually compile:
MetricTest::testEventColumnsConstantpins the column list and failed until updated — that guard did its job. Unit/schema/column-type tests green (56 tests, 288 assertions), Pint clean, PHPStan level max clean.Not run: the ClickHouse e2e suite — no Docker daemon on this box. The DDL and query construction are verified above; actual execution against ClickHouse is CI's.