-
Notifications
You must be signed in to change notification settings - Fork 1.9k
feat(crm): Create GroupUsageMetric model #36706
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Greptile Summary
This PR introduces a comprehensive GroupUsageMetric system to support CRM usage metrics configuration as part of issue #36664. The implementation creates a new Django model GroupUsageMetric
that stores configurable usage metrics associated with specific group types within teams.
The model includes several key components:
- Configuration fields: Format options (numeric/currency), time intervals (week/month/quarter), and display types (number/sparkline)
- Filter system: JSON fields for storing filters, compiled bytecode, and error handling
- Team scoping: Foreign key relationship to Team model with CASCADE deletion
- Unique constraints: Prevents duplicate metric names within the same group type
The PR also introduces a new UUID7Model
base class that uses the modern RFC9562 UUIDv7 specification for time-sortable primary keys, replacing the older UUIDT pattern for new models.
On the API side, a complete GroupUsageMetricViewSet
provides CRUD operations through a nested REST endpoint structure (/api/projects/{id}/groups_types/{index}/metrics
). The implementation includes different serializers for list vs detail operations and proper team-based authorization.
The frontend integration updates crmUsageMetricsConfigLogic
from mock data to real API calls, changing the data structure to match the backend model (numeric intervals instead of strings, filters object instead of events array) and following standard PostHog patterns for API integration.
Comprehensive test coverage ensures all CRUD operations work correctly with proper authorization and validation. The migration creates the necessary database structure with appropriate indexes and constraints.
Confidence score: 3/5
- This PR has several implementation concerns that could cause issues in production
- Score lowered due to potential constraint and scoping issues in the model design
- Pay close attention to the GroupUsageMetric model unique constraint and team scoping logic
Context used:
Rule - Use Django's .get()
method instead of filtering and checking length when you expect exactly one object. This will automatically raise an exception if the object is not found. (link)
Context - Use 'None' as the default value for JSONField in models instead of 'dict()' to avoid creating a new dict instance for each model. (link)
9 files reviewed, 8 comments
posthog/migrations/0823_groupusagemetric_groupusagemetric_unique_metric_name.py
Outdated
Show resolved
Hide resolved
return await api.create(values.metricsUrl, metric) | ||
}, | ||
updateUsageMetric: async ({ metric }) => { | ||
// Mock API delay | ||
await new Promise((resolve) => setTimeout(resolve, 300)) | ||
const updatedMetrics = values.usageMetrics.map((oldMetric) => | ||
oldMetric.id === metric.id ? { ...oldMetric, ...metric } : oldMetric | ||
) | ||
return updatedMetrics | ||
return await api.update(`${values.metricsUrl}/${metric.id}`, metric) | ||
}, | ||
removeUsageMetric: async ({ id }) => { | ||
// Mock API delay | ||
await new Promise((resolve) => setTimeout(resolve, 300)) | ||
return values.usageMetrics.filter((metric) => metric.id !== id) | ||
return await api.delete(`${values.metricsUrl}/${id}`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: The add/update/remove operations return different response types than expected by the loader pattern - they should return the updated array of metrics, not individual responses
self.assertEqual(response.json(), self.permission_denied_response("You don't have access to the project.")) | ||
|
||
def test_unauthorized_metric_creation(self): | ||
other_url = f"/api/projects/{self.other_team.id}/groups_types/{str(self.other_group_type.pk)}/metrics" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Consider using group_type_index
instead of pk
for consistency with other URLs in the test that use group_type_index
.
NUMBER = "number", "number" | ||
SPARKLINE = "sparkline", "sparkline" | ||
|
||
team = models.ForeignKey("Team", on_delete=models.CASCADE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Missing related_name
attribute for the team foreign key. Consider adding related_name='group_usage_metrics'
to avoid potential conflicts.
e5ef89e
to
2d9a1fd
Compare
Size Change: 0 B Total Size: 2.68 MB ℹ️ View Unchanged
|
📸 UI snapshots have been updated4 snapshot changes in total. 0 added, 4 modified, 0 deleted:
Triggered by this commit. |
📸 UI snapshots have been updated3 snapshot changes in total. 0 added, 3 modified, 0 deleted:
Triggered by this commit. |
📸 UI snapshots have been updated2 snapshot changes in total. 0 added, 2 modified, 0 deleted:
Triggered by this commit. |
📸 UI snapshots have been updated2 snapshot changes in total. 0 added, 2 modified, 0 deleted:
Triggered by this commit. |
📸 UI snapshots have been updated5 snapshot changes in total. 0 added, 5 modified, 0 deleted:
Triggered by this commit. |
📸 UI snapshots have been updated5 snapshot changes in total. 0 added, 5 modified, 0 deleted:
Triggered by this commit. |
📸 UI snapshots have been updated6 snapshot changes in total. 0 added, 6 modified, 0 deleted:
Triggered by this commit. |
📸 UI snapshots have been updated3 snapshot changes in total. 0 added, 3 modified, 0 deleted:
Triggered by this commit. |
📸 UI snapshots have been updated3 snapshot changes in total. 0 added, 3 modified, 0 deleted:
Triggered by this commit. |
📸 UI snapshots have been updated1 snapshot changes in total. 0 added, 1 modified, 0 deleted:
Triggered by this commit. |
📸 UI snapshots have been updated3 snapshot changes in total. 0 added, 3 modified, 0 deleted:
Triggered by this commit. |
📸 UI snapshots have been updated2 snapshot changes in total. 0 added, 2 modified, 0 deleted:
Triggered by this commit. |
📸 UI snapshots have been updated6 snapshot changes in total. 0 added, 6 modified, 0 deleted:
Triggered by this commit. |
📸 UI snapshots have been updated4 snapshot changes in total. 0 added, 4 modified, 0 deleted:
Triggered by this commit. |
📸 UI snapshots have been updated1 snapshot changes in total. 0 added, 1 modified, 0 deleted:
Triggered by this commit. |
📸 UI snapshots have been updated2 snapshot changes in total. 0 added, 2 modified, 0 deleted:
Triggered by this commit. |
📸 UI snapshots have been updated5 snapshot changes in total. 0 added, 5 modified, 0 deleted:
Triggered by this commit. |
📸 UI snapshots have been updated3 snapshot changes in total. 0 added, 3 modified, 0 deleted:
Triggered by this commit. |
omg, didn't realize this was on a loop. will rebase asap |
fb6b9d4
to
a3c2269
Compare
📸 UI snapshots have been updated6 snapshot changes in total. 0 added, 6 modified, 0 deleted:
Triggered by this commit. |
📸 UI snapshots have been updated2 snapshot changes in total. 0 added, 2 modified, 0 deleted:
Triggered by this commit. |
1720021
to
9299688
Compare
Migration SQL ChangesHey 👋, we've detected some migrations on this PR. Here's the SQL output for each migration, make sure they make sense:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It all makes sense to me! Left a couple comments, review them before merging
filters = models.JSONField(null=True, blank=True) | ||
bytecode = models.JSONField(null=True, blank=True) | ||
bytecode_error = models.TextField(null=True, blank=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please confirm with Ben whether these are the right types.
Also, does it make sense to make these nullable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll make these non-nullable, as it is easier to move to nullable later if needed.
About the types, I'm reproducing what's been done for cohorts, this should be pretty similar.
Problem
Closes #36664
Changes
GroupUsageMetric
modelGroupUsageMetricViewSet
crmUsageMetricsConfigLogic
with backendHow did you test this code?
👉 Stay up-to-date with PostHog coding conventions for a smoother review.