Skip to content

feat(gql): Add BillableMetricId field on Alert object #3692

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

Merged
merged 2 commits into from
May 21, 2025

Conversation

julienbourdeau
Copy link
Contributor

Context

In the frontend, we want to filter the list of alert_type and billable_metric_id to remove those that already exists.

Description

In GQL, when an object as a realtionship, we typically add this as a full object. I'm also adding the billabled_metric_id on Alert object which as 2 benefits:

  1. we avoid N+1 loading billable metric just to get the ID
  2. the response shape is much nicer (no nested hash)
[
  {
    "alertType": "billable_metric_usage_amount",
    "billableMetricId": "b86641de-bcb4-4be7-abf8-57c3f9905213"
  },
  {
    "alertType": "usage_amount",
    "billableMetricId": null
  }
]

In order to check if an alert exists, we can loop over the array like this

h.any? { it["alertType"] == "usage_amount" }

CleanShot 2025-05-21 at 12 36 11@2x

@julienbourdeau julienbourdeau self-assigned this May 21, 2025
@julienbourdeau julienbourdeau force-pushed the gql/alert-billable_metric_id branch from 30db8ab to 61aa457 Compare May 21, 2025 10:41
@julienbourdeau julienbourdeau merged commit 17a239e into main May 21, 2025
14 checks passed
@julienbourdeau julienbourdeau deleted the gql/alert-billable_metric_id branch May 21, 2025 12:36
julienbourdeau added a commit that referenced this pull request May 28, 2025
## Context

By design, GraphQL can create a lot of N+1. The `graphql` gem provides
[a way to inspect the
query](https://graphql-ruby.org/queries/lookahead.html) and eager load
accordingly.

This is generally not an issue because Lago's vast majority of the load
is on the REST API, not the dashboard. But still, it can lead to slow
page loading.

## Description

In the SubscriptionAlertResolver, a query can trigger 2 extra requests
per item, one for thresholds, one for billable metric.

With lookahead, we check if billableMetric or thresholds are request end
ensure we call `includes(:billable_metric)` or `includes(:thresholds)`
respectively.

Another useful technique is to also [expose ids alongside the
relationships](#3692), like in
the Alert Object.

### How to use

* Add `extras` Resolver configuration
* Make sure you receive the `lookahead:` named argument
* Explore lookahead object with `selection` and `select?`

```ruby
class SomethingResolver < Resolvers::BaseResolver
  REQUIRED_PERMISSION = "something:view"

  # Explicitly tell GraphQL that you want to use `lookahead`
  extras [:lookahead]

  argument :limit, Integer, required: false
  argument :page, Integer, required: false

  type Types::Something::Object.collection_type, null: false

  def resolve(lookahead:, limit: nil, page: nil) # Automatically receive lookahead
    scope = Something.all

    # Notice the :collection outer object
    if lookahead.selection(:collection).selects?(:organization)
      scope = scope.includes(:organization)
    end

    if lookahead.selection(:collection).selection(:organization).selects?(:billing_entity)
      scope = scope.includes(organization: :billing_entity)
    end

    scope
  end
end
```

Links:
* https://graphql-ruby.org/queries/lookahead.html
*
https://gist.github.com/DmitryTsepelev/d0d4f52b1d0a0f6acf3c5894b11a52ca

### Testing

This PR also sets up [`bullet` gem for
tests](https://github.com/flyerhzm/bullet?tab=readme-ov-file#run-in-tests),
until now it was only a dev dependency.

Notice the `with_bullet: true` example metadata.

```ruby
context "when doing something" do
  let(:model) { ... }

  it "does something", with_bullet: true do
    #...

    # Must be start manually for maximum flexibility
    Bullet.start_request

    subject # Service.call, gql query, api requests, function call...

    expect(Bullet.notification?).to eq false # Ensure no N+1 !
  end
end
```
diegocharles pushed a commit that referenced this pull request Jun 2, 2025
## Context

By design, GraphQL can create a lot of N+1. The `graphql` gem provides
[a way to inspect the
query](https://graphql-ruby.org/queries/lookahead.html) and eager load
accordingly.

This is generally not an issue because Lago's vast majority of the load
is on the REST API, not the dashboard. But still, it can lead to slow
page loading.

## Description

In the SubscriptionAlertResolver, a query can trigger 2 extra requests
per item, one for thresholds, one for billable metric.

With lookahead, we check if billableMetric or thresholds are request end
ensure we call `includes(:billable_metric)` or `includes(:thresholds)`
respectively.

Another useful technique is to also [expose ids alongside the
relationships](#3692), like in
the Alert Object.

### How to use

* Add `extras` Resolver configuration
* Make sure you receive the `lookahead:` named argument
* Explore lookahead object with `selection` and `select?`

```ruby
class SomethingResolver < Resolvers::BaseResolver
  REQUIRED_PERMISSION = "something:view"

  # Explicitly tell GraphQL that you want to use `lookahead`
  extras [:lookahead]

  argument :limit, Integer, required: false
  argument :page, Integer, required: false

  type Types::Something::Object.collection_type, null: false

  def resolve(lookahead:, limit: nil, page: nil) # Automatically receive lookahead
    scope = Something.all

    # Notice the :collection outer object
    if lookahead.selection(:collection).selects?(:organization)
      scope = scope.includes(:organization)
    end

    if lookahead.selection(:collection).selection(:organization).selects?(:billing_entity)
      scope = scope.includes(organization: :billing_entity)
    end

    scope
  end
end
```

Links:
* https://graphql-ruby.org/queries/lookahead.html
*
https://gist.github.com/DmitryTsepelev/d0d4f52b1d0a0f6acf3c5894b11a52ca

### Testing

This PR also sets up [`bullet` gem for
tests](https://github.com/flyerhzm/bullet?tab=readme-ov-file#run-in-tests),
until now it was only a dev dependency.

Notice the `with_bullet: true` example metadata.

```ruby
context "when doing something" do
  let(:model) { ... }

  it "does something", with_bullet: true do
    #...

    # Must be start manually for maximum flexibility
    Bullet.start_request

    subject # Service.call, gql query, api requests, function call...

    expect(Bullet.notification?).to eq false # Ensure no N+1 !
  end
end
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants