+
{{ s__('UsageBilling|Last updated:') }}
diff --git a/ee/app/assets/javascripts/usage_quotas/usage_billing/graphql/get_subscription_usage.query.graphql b/ee/app/assets/javascripts/usage_quotas/usage_billing/graphql/get_subscription_usage.query.graphql
index 7a35c9723f85e95d6bc7d3797d19b177e2c4ca5e..f944dfbac66c19edaab833fa5caf6d7dfab2101f 100644
--- a/ee/app/assets/javascripts/usage_quotas/usage_billing/graphql/get_subscription_usage.query.graphql
+++ b/ee/app/assets/javascripts/usage_quotas/usage_billing/graphql/get_subscription_usage.query.graphql
@@ -1,6 +1,6 @@
query getSubscriptionUsage($namespacePath: ID) {
subscriptionUsage(namespacePath: $namespacePath) {
- lastUpdated
+ lastEventTransactionAt
purchaseCreditsPath
poolUsage {
totalCredits
diff --git a/ee/app/assets/javascripts/usage_quotas/usage_billing/users/show/components/app.vue b/ee/app/assets/javascripts/usage_quotas/usage_billing/users/show/components/app.vue
index 88e09f4302d1c0f90444c2e2e7fd9e0f97b25a98..be0ead6b1230e2fae3b2dd54dc90e51171f591dd 100644
--- a/ee/app/assets/javascripts/usage_quotas/usage_billing/users/show/components/app.vue
+++ b/ee/app/assets/javascripts/usage_quotas/usage_billing/users/show/components/app.vue
@@ -146,7 +146,7 @@ export default {
{{ s__('UsageBilling|Last updated:') }}
diff --git a/ee/app/assets/javascripts/usage_quotas/usage_billing/users/show/graphql/get_user_subscription_usage.query.graphql b/ee/app/assets/javascripts/usage_quotas/usage_billing/users/show/graphql/get_user_subscription_usage.query.graphql
index 03dfea02c00966c1a7fc58a7b93e1e6044b8def6..52109d38a94659fcac6289ffef9c4b3f4ccae615 100644
--- a/ee/app/assets/javascripts/usage_quotas/usage_billing/users/show/graphql/get_user_subscription_usage.query.graphql
+++ b/ee/app/assets/javascripts/usage_quotas/usage_billing/users/show/graphql/get_user_subscription_usage.query.graphql
@@ -1,6 +1,6 @@
query getUserSubscriptionUsage($namespacePath: ID, $username: String) {
subscriptionUsage(namespacePath: $namespacePath) {
- lastUpdated
+ lastEventTransactionAt
startDate
endDate
diff --git a/ee/app/graphql/types/gitlab_subscriptions/subscription_usage/one_time_credits_type.rb b/ee/app/graphql/types/gitlab_subscriptions/subscription_usage/one_time_credits_type.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5f6732d9ccf97735b82055adc7b3526697a493ff
--- /dev/null
+++ b/ee/app/graphql/types/gitlab_subscriptions/subscription_usage/one_time_credits_type.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Types
+ module GitlabSubscriptions
+ module SubscriptionUsage
+ class OneTimeCreditsType < BaseObject
+ graphql_name 'GitlabSubscriptionOneTimeCredits'
+ description 'Describes the usage of one time credits for the subscription'
+
+ authorize :read_subscription_usage
+
+ field :credits_used,
+ type: GraphQL::Types::Float,
+ null: true,
+ description: 'GitLab Credits used from the one time credits allocation.'
+
+ # rubocop: disable GraphQL/ExtractType -- no value for now
+ field :total_credits,
+ type: GraphQL::Types::Float,
+ null: true,
+ description: 'Total of GitLab Credits allocated as one time credits.'
+
+ field :total_credits_remaining,
+ type: GraphQL::Types::Float,
+ null: true,
+ description: 'Total of GitLab Credits remaining from the one time credits allocation.'
+ # rubocop:enable GraphQL/ExtractType
+ end
+ end
+ end
+end
diff --git a/ee/app/graphql/types/gitlab_subscriptions/subscription_usage/users_usage_type.rb b/ee/app/graphql/types/gitlab_subscriptions/subscription_usage/users_usage_type.rb
index 411e72cf6ef675af76ea05ee9a57f679601d804f..1b1b988c2e7380932c0ffbfa62d0367741b9d897 100644
--- a/ee/app/graphql/types/gitlab_subscriptions/subscription_usage/users_usage_type.rb
+++ b/ee/app/graphql/types/gitlab_subscriptions/subscription_usage/users_usage_type.rb
@@ -10,16 +10,23 @@ class UsersUsageType < BaseObject
authorize :read_subscription_usage
# rubocop: disable GraphQL/ExtractType -- no value for now
- field :total_users_using_credits, GraphQL::Types::Int, null: true,
+ field :total_users_using_credits, GraphQL::Types::Int,
+ null: true,
description: 'Total number of users consuming GitLab Credits.'
- field :total_users_using_pool, GraphQL::Types::Int, null: true,
+ field :total_users_using_pool, GraphQL::Types::Int,
+ null: true,
description: 'Total number of users consuming pool GitLab Credits.'
- field :total_users_using_overage, GraphQL::Types::Int, null: true,
+ field :total_users_using_overage, GraphQL::Types::Int,
+ null: true,
description: 'Total number of users consuming overage.'
# rubocop:enable GraphQL/ExtractType
+ field :credits_used, GraphQL::Types::Float,
+ null: true,
+ description: 'GitLab Credits used by consumers of the subscription.'
+
field :daily_usage,
[DailyUsageType],
null: true,
diff --git a/ee/app/graphql/types/gitlab_subscriptions/subscription_usage_type.rb b/ee/app/graphql/types/gitlab_subscriptions/subscription_usage_type.rb
index c66e25a1fa613624b31d71d5bace7a97650efe97..808f30d175827f2ab28cda9e4d299f59bb315405 100644
--- a/ee/app/graphql/types/gitlab_subscriptions/subscription_usage_type.rb
+++ b/ee/app/graphql/types/gitlab_subscriptions/subscription_usage_type.rb
@@ -8,9 +8,9 @@ class SubscriptionUsageType < BaseObject
authorize :read_subscription_usage
- field :last_updated, GraphQL::Types::ISO8601DateTime,
+ field :last_event_transaction_at, GraphQL::Types::ISO8601DateTime,
null: true,
- description: 'Date and time when the usage data was last updated.'
+ description: 'Date and time when the last usage event resulted in a wallet transaction.'
field :start_date, GraphQL::Types::ISO8601Date,
null: true,
@@ -24,6 +24,10 @@ class SubscriptionUsageType < BaseObject
null: true,
description: 'URL to purchase GitLab Credits.'
+ field :one_time_credits, SubscriptionUsage::OneTimeCreditsType,
+ null: true,
+ description: 'One time credits usage for the subscription.'
+
field :pool_usage, SubscriptionUsage::PoolUsageType,
null: true,
description: 'Consumption usage for the subscription shared pool.'
diff --git a/ee/lib/gitlab/subscription_portal/subscription_usage_client.rb b/ee/lib/gitlab/subscription_portal/subscription_usage_client.rb
index 72c5f2bb72edbb79cefd46aa7b11811c7e6aa118..842c08c9bf8f5c91635e590b8ae61f9de3d04532 100644
--- a/ee/lib/gitlab/subscription_portal/subscription_usage_client.rb
+++ b/ee/lib/gitlab/subscription_portal/subscription_usage_client.rb
@@ -16,13 +16,32 @@ class SubscriptionUsageClient < Client
gitlabCreditsUsage {
startDate
endDate
- lastUpdated
+ lastEventTransactionAt
purchaseCreditsPath
}
}
}
GQL
+ GET_ONE_TIME_CREDITS_QUERY = <<~GQL
+ query subscriptionUsage(
+ $namespaceId: ID,
+ $licenseKey: String,
+ $startDate: ISO8601Date,
+ $endDate: ISO8601Date
+ ) {
+ subscription(namespaceId: $namespaceId, licenseKey: $licenseKey) {
+ gitlabCreditsUsage(startDate: $startDate, endDate: $endDate) {
+ oneTimeCredits {
+ creditsUsed
+ totalCredits
+ totalCreditsRemaining
+ }
+ }
+ }
+ }
+ GQL
+
GET_POOL_USAGE_QUERY = <<~GQL
query subscriptionUsage(
$namespaceId: ID,
@@ -131,6 +150,7 @@ class SubscriptionUsageClient < Client
totalUsersUsingCredits
totalUsersUsingPool
totalUsersUsingOverage
+ creditsUsed
dailyUsage {
date
creditsUsed
@@ -177,6 +197,22 @@ def get_metadata
end
strong_memoize_attr :get_metadata
+ def get_one_time_credits
+ response = execute_graphql_query(
+ query: GET_ONE_TIME_CREDITS_QUERY,
+ variables: default_variables.merge(startDate: start_date, endDate: end_date)
+ )
+
+ if unsuccessful_response?(response)
+ error(GET_ONE_TIME_CREDITS_QUERY, response)
+ else
+ {
+ success: true,
+ oneTimeCredits: response.dig(:data, :subscription, :gitlabCreditsUsage, :oneTimeCredits)
+ }
+ end
+ end
+
def get_pool_usage
response = execute_graphql_query(
query: GET_POOL_USAGE_QUERY,
diff --git a/ee/lib/gitlab_subscriptions/subscription_usage.rb b/ee/lib/gitlab_subscriptions/subscription_usage.rb
index 72ec9695b07afbd5990de3730c0ff2781aae2ecd..20a34242d8fef0bcdeb4e8bedff46fc3d93f6e01 100644
--- a/ee/lib/gitlab_subscriptions/subscription_usage.rb
+++ b/ee/lib/gitlab_subscriptions/subscription_usage.rb
@@ -4,6 +4,7 @@ module GitlabSubscriptions
class SubscriptionUsage
include ::Gitlab::Utils::StrongMemoize
+ OneTimeCredits = Struct.new(:credits_used, :total_credits, :total_credits_remaining, :declarative_policy_subject)
PoolUsage = Struct.new(:total_credits, :credits_used, :daily_usage, :declarative_policy_subject)
Overage = Struct.new(:is_allowed, :credits_used, :daily_usage, :declarative_policy_subject)
DailyUsage = Struct.new(:date, :credits_used, :declarative_policy_subject)
@@ -28,14 +29,28 @@ def end_date
usage_metadata[:endDate]
end
- def last_updated
- usage_metadata[:lastUpdated]
+ def last_event_transaction_at
+ usage_metadata[:lastEventTransactionAt]
end
def purchase_credits_path
usage_metadata[:purchaseCreditsPath]
end
+ def one_time_credits
+ one_time_credits_response = subscription_usage_client.get_one_time_credits
+
+ return unless one_time_credits_response[:success]
+
+ OneTimeCredits.new(
+ credits_used: one_time_credits_response.dig(:oneTimeCredits, :creditsUsed),
+ total_credits: one_time_credits_response.dig(:oneTimeCredits, :totalCredits),
+ total_credits_remaining: one_time_credits_response.dig(:oneTimeCredits, :totalCreditsRemaining),
+ declarative_policy_subject: self
+ )
+ end
+ strong_memoize_attr :one_time_credits
+
def pool_usage
pool_usage_response = subscription_usage_client.get_pool_usage
diff --git a/ee/lib/gitlab_subscriptions/subscriptions_usage/user_usage.rb b/ee/lib/gitlab_subscriptions/subscriptions_usage/user_usage.rb
index 24adec5a336461b531e0629f6d35bdd711957ef5..596a2e2360559412f0b728f2f66070c111fee143 100644
--- a/ee/lib/gitlab_subscriptions/subscriptions_usage/user_usage.rb
+++ b/ee/lib/gitlab_subscriptions/subscriptions_usage/user_usage.rb
@@ -32,6 +32,10 @@ def total_users_using_overage
usage_stats[:totalUsersUsingOverage]
end
+ def credits_used
+ usage_stats[:creditsUsed]
+ end
+
def users(username: nil)
strong_memoize_with(:users, username) do
case subscription_usage.subscription_target
diff --git a/ee/spec/frontend/usage_quotas/usage_billing/components/app_spec.js b/ee/spec/frontend/usage_quotas/usage_billing/components/app_spec.js
index 04bc76d234edbcde936ab30118098494e911d3de..c7af4742043f4136197e0cfa2bd3f088cc6a3d06 100644
--- a/ee/spec/frontend/usage_quotas/usage_billing/components/app_spec.js
+++ b/ee/spec/frontend/usage_quotas/usage_billing/components/app_spec.js
@@ -22,7 +22,7 @@ import {
usageDataNoPoolNoOverage,
usageDataNoPoolWithOverage,
usageDataWithPool,
- usageDataWithoutLastUpdated,
+ usageDataWithoutLastEventTransactionAt,
} from '../mock_data';
jest.mock('~/lib/logger');
@@ -118,10 +118,10 @@ describe('UsageBillingApp', () => {
});
});
- describe('when lastUpdated is not provided', () => {
+ describe('when lastEventTransactionAt is not provided', () => {
beforeEach(async () => {
createComponent({
- mockQueryHandler: jest.fn().mockResolvedValue(usageDataWithoutLastUpdated),
+ mockQueryHandler: jest.fn().mockResolvedValue(usageDataWithoutLastEventTransactionAt),
});
await waitForPromises();
});
diff --git a/ee/spec/frontend/usage_quotas/usage_billing/mock_data.js b/ee/spec/frontend/usage_quotas/usage_billing/mock_data.js
index e90f3cc144e7cc744ecb4f1ad3302fd7349c9ad3..f8e40e0ea42e6bdbf46a7b61ff12955a8920e658 100644
--- a/ee/spec/frontend/usage_quotas/usage_billing/mock_data.js
+++ b/ee/spec/frontend/usage_quotas/usage_billing/mock_data.js
@@ -63,7 +63,7 @@ export const mockUsageDataWithoutPool = {
export const mockUsersUsageDataWithoutPool = {
data: {
subscriptionUsage: {
- lastUpdated: '2024-01-15T10:30:00Z',
+ lastEventTransactionAt: '2024-01-15T10:30:00Z',
purchaseCreditsPath: '/purchase-credits-path',
usersUsage: {
// overall statistics
@@ -218,7 +218,7 @@ export const mockUsageDataWithPool = {
export const mockUsersUsageDataWithPool = {
data: {
subscriptionUsage: {
- lastUpdated: '2025-10-14T07:41:59Z',
+ lastEventTransactionAt: '2025-10-14T07:41:59Z',
purchaseCreditsPath: '/purchase-credits-path',
usersUsage: {
// overall statistics
@@ -400,7 +400,7 @@ export const mockUsageDataWithOverage = {
export const mockUsersUsageDataWithOverage = {
data: {
subscriptionUsage: {
- lastUpdated: '2025-10-14T07:41:59Z',
+ lastEventTransactionAt: '2025-10-14T07:41:59Z',
purchaseCreditsPath: '/purchase-credits-path',
usersUsage: {
totalUsers: 50,
@@ -544,7 +544,7 @@ export const mockUsageDataWithZeroAllocation = {
export const mockUsersUsageDataWithZeroAllocation = {
data: {
subscriptionUsage: {
- lastUpdated: '2025-10-14T07:41:59Z',
+ lastEventTransactionAt: '2025-10-14T07:41:59Z',
purchaseCreditsPath: '/purchase-credits-path',
usersUsage: {
totalUsers: 5,
@@ -630,7 +630,7 @@ export const mockUsersUsageDataWithZeroAllocation = {
export const usageDataWithPool = {
data: {
subscriptionUsage: {
- lastUpdated: '2025-10-14T07:41:59Z',
+ lastEventTransactionAt: '2025-10-14T07:41:59Z',
purchaseCreditsPath: '/purchase-credits-path',
poolUsage: {
creditsUsed: 50,
@@ -644,7 +644,7 @@ export const usageDataWithPool = {
},
};
-export const usageDataWithoutLastUpdated = {
+export const usageDataWithoutLastEventTransactionAt = {
data: {
subscriptionUsage: {
poolUsage: {
@@ -659,7 +659,7 @@ export const usageDataWithPoolWithOverage = {
data: {
subscriptionUsage: {
purchaseCreditsPath: '/purchase-credits-path',
- lastUpdated: '2025-10-14T07:41:59Z',
+ lastEventTransactionAt: '2025-10-14T07:41:59Z',
poolUsage: {
creditsUsed: 300,
totalCredits: 300,
@@ -675,7 +675,7 @@ export const usageDataWithPoolWithOverage = {
export const usageDataNoPoolNoOverage = {
data: {
subscriptionUsage: {
- lastUpdated: '2024-01-15T10:30:00Z',
+ lastEventTransactionAt: '2024-01-15T10:30:00Z',
purchaseCreditsPath: '/purchase-credits-path',
poolUsage: null,
overage: {
@@ -689,7 +689,7 @@ export const usageDataNoPoolNoOverage = {
export const usageDataNoPoolWithOverage = {
data: {
subscriptionUsage: {
- lastUpdated: '2024-01-15T10:30:00Z',
+ lastEventTransactionAt: '2024-01-15T10:30:00Z',
purchaseCreditsPath: '/purchase-credits-path',
poolUsage: null,
overage: {
diff --git a/ee/spec/frontend/usage_quotas/usage_billing/users/show/mock_data.js b/ee/spec/frontend/usage_quotas/usage_billing/users/show/mock_data.js
index 6e5f64a15d6a821ef52fa046419e2291189ba78a..efa4f824f9bfea789ece133397dc301449c20dfd 100644
--- a/ee/spec/frontend/usage_quotas/usage_billing/users/show/mock_data.js
+++ b/ee/spec/frontend/usage_quotas/usage_billing/users/show/mock_data.js
@@ -1,7 +1,7 @@
export const mockDataWithPool = {
data: {
subscriptionUsage: {
- lastUpdated: '2025-02-02T18:45:32Z',
+ lastEventTransactionAt: '2025-02-02T18:45:32Z',
startDate: '2025-08-01',
endDate: '2025-08-31',
diff --git a/ee/spec/graphql/types/gitlab_subscriptions/subscription_usage/one_time_credits_type_spec.rb b/ee/spec/graphql/types/gitlab_subscriptions/subscription_usage/one_time_credits_type_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6c1e3a5aabb45e785ed56047d29acea210fb4161
--- /dev/null
+++ b/ee/spec/graphql/types/gitlab_subscriptions/subscription_usage/one_time_credits_type_spec.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['GitlabSubscriptionOneTimeCredits'], feature_category: :consumables_cost_management do
+ it { expect(described_class.graphql_name).to eq('GitlabSubscriptionOneTimeCredits') }
+ it { expect(described_class).to require_graphql_authorizations(:read_subscription_usage) }
+
+ it 'has expected fields' do
+ expect(described_class).to have_graphql_fields([:credits_used, :total_credits, :total_credits_remaining])
+ end
+end
diff --git a/ee/spec/graphql/types/gitlab_subscriptions/subscription_usage/users_usage_type_spec.rb b/ee/spec/graphql/types/gitlab_subscriptions/subscription_usage/users_usage_type_spec.rb
index cb6a7358c5265c7c46581bbf24711bd01b5b70a2..c4bd6e5fec3b9a80a38cc0693c8cc5df3c2813d4 100644
--- a/ee/spec/graphql/types/gitlab_subscriptions/subscription_usage/users_usage_type_spec.rb
+++ b/ee/spec/graphql/types/gitlab_subscriptions/subscription_usage/users_usage_type_spec.rb
@@ -11,6 +11,7 @@
:total_users_using_credits,
:total_users_using_pool,
:total_users_using_overage,
+ :credits_used,
:daily_usage,
:users
])
diff --git a/ee/spec/graphql/types/gitlab_subscriptions/subscription_usage_type_spec.rb b/ee/spec/graphql/types/gitlab_subscriptions/subscription_usage_type_spec.rb
index 0513a0bb825b3b2c12caa7d58a71a314fa6eeaa8..ac4ba8b3c475901404563759fefe9dc15dd4862a 100644
--- a/ee/spec/graphql/types/gitlab_subscriptions/subscription_usage_type_spec.rb
+++ b/ee/spec/graphql/types/gitlab_subscriptions/subscription_usage_type_spec.rb
@@ -8,10 +8,11 @@
it 'has expected fields' do
expect(described_class).to have_graphql_fields([
- :last_updated,
+ :last_event_transaction_at,
:start_date,
:end_date,
:purchase_credits_path,
+ :one_time_credits,
:pool_usage,
:overage,
:users_usage
diff --git a/ee/spec/lib/gitlab/subscription_portal/subscription_usage_client_spec.rb b/ee/spec/lib/gitlab/subscription_portal/subscription_usage_client_spec.rb
index ed79d6a0ae2e4b9aba0586d80227d4237227aff0..e63d328183dd2a3c100e48d3055e6dfcbbab1152 100644
--- a/ee/spec/lib/gitlab/subscription_portal/subscription_usage_client_spec.rb
+++ b/ee/spec/lib/gitlab/subscription_portal/subscription_usage_client_spec.rb
@@ -137,287 +137,317 @@
end
describe '#get_metadata' do
- context 'when the subscription portal response is successful' do
- let(:request) { client.get_metadata }
- let(:query) { described_class::GET_METADATA_QUERY }
- let(:portal_response) do
- {
- success: true,
- data: {
- subscription: {
- gitlabCreditsUsage: {
- startDate: "2025-10-01",
- endDate: "2025-10-31",
- lastUpdated: "2025-10-01T16:19:59Z",
- purchaseCreditsPath: '/mock/path'
- }
+ let(:request) { client.get_metadata }
+ let(:query) { described_class::GET_METADATA_QUERY }
+ let(:portal_response) do
+ {
+ success: true,
+ data: {
+ subscription: {
+ gitlabCreditsUsage: {
+ startDate: "2025-10-01",
+ endDate: "2025-10-31",
+ lastEventTransactionAt: "2025-10-01T16:19:59Z",
+ purchaseCreditsPath: '/mock/path'
}
}
}
- end
+ }
+ end
- let(:expected_response) do
- {
- success: true,
- subscriptionUsage: {
- startDate: "2025-10-01",
- endDate: "2025-10-31",
- lastUpdated: "2025-10-01T16:19:59Z",
- purchaseCreditsPath: '/mock/path'
+ let(:expected_response) do
+ {
+ success: true,
+ subscriptionUsage: {
+ startDate: "2025-10-01",
+ endDate: "2025-10-31",
+ lastEventTransactionAt: "2025-10-01T16:19:59Z",
+ purchaseCreditsPath: '/mock/path'
+ }
+ }
+ end
+
+ include_context 'for self-managed request' do
+ let(:variables) { { licenseKey: license_key } }
+ end
+
+ include_context 'for gitlab.com request' do
+ let(:variables) { { namespaceId: namespace_id } }
+ end
+ end
+
+ describe '#get_one_time_credits' do
+ let(:request) { client.get_one_time_credits }
+ let(:query) { described_class::GET_ONE_TIME_CREDITS_QUERY }
+ let(:one_time_credits) do
+ {
+ creditsUsed: 12.25,
+ totalCredits: 1000,
+ totalCreditsRemaining: 987.75
+ }
+ end
+
+ let(:portal_response) do
+ {
+ success: true,
+ data: {
+ subscription: {
+ gitlabCreditsUsage: {
+ oneTimeCredits: one_time_credits
+ }
}
}
- end
+ }
+ end
- include_context 'for self-managed request' do
- let(:variables) { { licenseKey: license_key } }
- end
+ let(:expected_response) do
+ {
+ success: true,
+ oneTimeCredits: one_time_credits
+ }
+ end
- include_context 'for gitlab.com request' do
- let(:variables) { { namespaceId: namespace_id } }
- end
+ include_context 'for self-managed request' do
+ let(:variables) { { licenseKey: license_key, startDate: start_date, endDate: end_date } }
+ end
+
+ include_context 'for gitlab.com request' do
+ let(:variables) { { namespaceId: namespace_id, startDate: start_date, endDate: end_date } }
end
end
describe '#get_pool_usage' do
- context 'when the subscription portal response is successful' do
- let(:request) { client.get_pool_usage }
- let(:query) { described_class::GET_POOL_USAGE_QUERY }
- let(:pool_usage) do
- {
- totalCredits: 1000,
- creditsUsed: 250,
- dailyUsage: [{ date: '2025-10-01', creditsUsed: 250 }]
- }
- end
+ let(:request) { client.get_pool_usage }
+ let(:query) { described_class::GET_POOL_USAGE_QUERY }
+ let(:pool_usage) do
+ {
+ totalCredits: 1000,
+ creditsUsed: 250,
+ dailyUsage: [{ date: '2025-10-01', creditsUsed: 250 }]
+ }
+ end
- let(:portal_response) do
- {
- success: true,
- data: {
- subscription: {
- gitlabCreditsUsage: {
- poolUsage: pool_usage
- }
+ let(:portal_response) do
+ {
+ success: true,
+ data: {
+ subscription: {
+ gitlabCreditsUsage: {
+ poolUsage: pool_usage
}
}
}
- end
+ }
+ end
- let(:expected_response) do
- {
- success: true,
- poolUsage: pool_usage
- }
- end
+ let(:expected_response) do
+ {
+ success: true,
+ poolUsage: pool_usage
+ }
+ end
- include_context 'for self-managed request' do
- let(:variables) { { licenseKey: license_key, startDate: start_date, endDate: end_date } }
- end
+ include_context 'for self-managed request' do
+ let(:variables) { { licenseKey: license_key, startDate: start_date, endDate: end_date } }
+ end
- include_context 'for gitlab.com request' do
- let(:variables) { { namespaceId: namespace_id, startDate: start_date, endDate: end_date } }
- end
+ include_context 'for gitlab.com request' do
+ let(:variables) { { namespaceId: namespace_id, startDate: start_date, endDate: end_date } }
end
end
describe '#get_overage_usage' do
- context 'when the subscription portal response is successful' do
- let(:request) { client.get_overage_usage }
- let(:query) { described_class::GET_OVERAGE_USAGE_QUERY }
- let(:overage) do
- {
- isAllowed: true,
- creditsUsed: 250,
- dailyUsage: [{ date: '2025-10-01', creditsUsed: 250 }]
- }
- end
+ let(:request) { client.get_overage_usage }
+ let(:query) { described_class::GET_OVERAGE_USAGE_QUERY }
+ let(:overage) do
+ {
+ isAllowed: true,
+ creditsUsed: 250,
+ dailyUsage: [{ date: '2025-10-01', creditsUsed: 250 }]
+ }
+ end
- let(:portal_response) do
- {
- success: true,
- data: {
- subscription: {
- gitlabCreditsUsage: {
- overage: overage
- }
+ let(:portal_response) do
+ {
+ success: true,
+ data: {
+ subscription: {
+ gitlabCreditsUsage: {
+ overage: overage
}
}
}
- end
+ }
+ end
- let(:expected_response) do
- {
- success: true,
- overage: overage
- }
- end
+ let(:expected_response) do
+ {
+ success: true,
+ overage: overage
+ }
+ end
- include_context 'for self-managed request' do
- let(:variables) { { licenseKey: license_key, startDate: start_date, endDate: end_date } }
- end
+ include_context 'for self-managed request' do
+ let(:variables) { { licenseKey: license_key, startDate: start_date, endDate: end_date } }
+ end
- include_context 'for gitlab.com request' do
- let(:variables) { { namespaceId: namespace_id, startDate: start_date, endDate: end_date } }
- end
+ include_context 'for gitlab.com request' do
+ let(:variables) { { namespaceId: namespace_id, startDate: start_date, endDate: end_date } }
end
end
describe '#get_events_for_user_id' do
- context 'when the subscription portal response is successful' do
- let(:user_id) { 123 }
- let(:page) { 1 }
- let(:request) { client.get_events_for_user_id(user_id, page) }
- let(:query) { described_class::GET_USER_EVENTS_QUERY }
- let(:user_events) do
- [
- {
- timestamp: "2025-10-01T16:25:28Z",
- eventType: "ai_token_usage",
- projectId: nil,
- namespaceId: nil,
- creditsUsed: 100
- },
- {
- timestamp: "2025-10-01T16:30:12Z",
- eventType: "workflow_execution",
- projectId: "19",
- namespaceId: "99",
- creditsUsed: 200
- }
- ]
- end
-
- let(:portal_response) do
+ let(:user_id) { 123 }
+ let(:page) { 1 }
+ let(:request) { client.get_events_for_user_id(user_id, page) }
+ let(:query) { described_class::GET_USER_EVENTS_QUERY }
+ let(:user_events) do
+ [
{
- success: true,
- data: {
- subscription: {
- gitlabCreditsUsage: {
- usersUsage: {
- users: [{ events: user_events }]
- }
+ timestamp: "2025-10-01T16:25:28Z",
+ eventType: "ai_token_usage",
+ projectId: nil,
+ namespaceId: nil,
+ creditsUsed: 100
+ },
+ {
+ timestamp: "2025-10-01T16:30:12Z",
+ eventType: "workflow_execution",
+ projectId: "19",
+ namespaceId: "99",
+ creditsUsed: 200
+ }
+ ]
+ end
+
+ let(:portal_response) do
+ {
+ success: true,
+ data: {
+ subscription: {
+ gitlabCreditsUsage: {
+ usersUsage: {
+ users: [{ events: user_events }]
}
}
}
}
- end
+ }
+ end
- let(:expected_response) do
- {
- success: true,
- userEvents: user_events
- }
- end
+ let(:expected_response) do
+ {
+ success: true,
+ userEvents: user_events
+ }
+ end
- include_context 'for self-managed request' do
- let(:variables) do
- { licenseKey: license_key, startDate: start_date, endDate: end_date, userIds: [user_id], page: page }
- end
+ include_context 'for self-managed request' do
+ let(:variables) do
+ { licenseKey: license_key, startDate: start_date, endDate: end_date, userIds: [user_id], page: page }
end
+ end
- include_context 'for gitlab.com request' do
- let(:variables) do
- { namespaceId: namespace_id, startDate: start_date, endDate: end_date, userIds: [user_id], page: page }
- end
+ include_context 'for gitlab.com request' do
+ let(:variables) do
+ { namespaceId: namespace_id, startDate: start_date, endDate: end_date, userIds: [user_id], page: page }
end
end
end
describe '#get_usage_for_user_ids' do
- context 'when the subscription portal response is successful' do
- let(:user_ids) { [123, 321] }
- let(:request) { client.get_usage_for_user_ids(user_ids) }
- let(:query) { described_class::GET_USERS_USAGE_QUERY }
- let(:users_usage) do
- [
- {
- userId: 123,
- totalCredits: 100,
- creditsUsed: 500,
- poolCreditsUsed: 400,
- overageCreditsUsed: 50
- },
- {
- userId: 321,
- totalCredits: 100,
- creditsUsed: 50,
- poolCreditsUsed: 0,
- overageCreditsUsed: 0
- }
- ]
- end
-
- let(:portal_response) do
+ let(:user_ids) { [123, 321] }
+ let(:request) { client.get_usage_for_user_ids(user_ids) }
+ let(:query) { described_class::GET_USERS_USAGE_QUERY }
+ let(:users_usage) do
+ [
{
- success: true,
- data: {
- subscription: {
- gitlabCreditsUsage: {
- usersUsage: { users: users_usage }
- }
+ userId: 123,
+ totalCredits: 100,
+ creditsUsed: 500,
+ poolCreditsUsed: 400,
+ overageCreditsUsed: 50
+ },
+ {
+ userId: 321,
+ totalCredits: 100,
+ creditsUsed: 50,
+ poolCreditsUsed: 0,
+ overageCreditsUsed: 0
+ }
+ ]
+ end
+
+ let(:portal_response) do
+ {
+ success: true,
+ data: {
+ subscription: {
+ gitlabCreditsUsage: {
+ usersUsage: { users: users_usage }
}
}
}
- end
+ }
+ end
- let(:expected_response) do
- {
- success: true,
- usersUsage: users_usage
- }
- end
+ let(:expected_response) do
+ {
+ success: true,
+ usersUsage: users_usage
+ }
+ end
- include_context 'for self-managed request' do
- let(:variables) { { licenseKey: license_key, startDate: start_date, endDate: end_date, userIds: user_ids } }
- end
+ include_context 'for self-managed request' do
+ let(:variables) { { licenseKey: license_key, startDate: start_date, endDate: end_date, userIds: user_ids } }
+ end
- include_context 'for gitlab.com request' do
- let(:variables) { { namespaceId: namespace_id, startDate: start_date, endDate: end_date, userIds: user_ids } }
- end
+ include_context 'for gitlab.com request' do
+ let(:variables) { { namespaceId: namespace_id, startDate: start_date, endDate: end_date, userIds: user_ids } }
end
end
describe '#get_users_usage_stats' do
- context 'when the subscription portal response is successful' do
- let(:request) { client.get_users_usage_stats }
- let(:query) { described_class::GET_USERS_USAGE_STATS_QUERY }
- let(:portal_response) do
- {
- success: true,
- data: {
- subscription: {
- gitlabCreditsUsage: {
- usersUsage: {
- totalUsersUsingCredits: 3,
- totalUsersUsingPool: 2,
- totalUsersUsingOverage: 1,
- dailyUsage: [{ date: '2025-10-01', creditsUsed: 321 }]
- }
+ let(:request) { client.get_users_usage_stats }
+ let(:query) { described_class::GET_USERS_USAGE_STATS_QUERY }
+ let(:portal_response) do
+ {
+ success: true,
+ data: {
+ subscription: {
+ gitlabCreditsUsage: {
+ usersUsage: {
+ totalUsersUsingCredits: 3,
+ totalUsersUsingPool: 2,
+ totalUsersUsingOverage: 1,
+ creditsUsed: 123.45,
+ dailyUsage: [{ date: '2025-10-01', creditsUsed: 321 }]
}
}
}
}
- end
+ }
+ end
- let(:expected_response) do
- {
- success: true,
- usersUsage: {
- totalUsersUsingCredits: 3,
- totalUsersUsingPool: 2,
- totalUsersUsingOverage: 1,
- dailyUsage: [{ date: '2025-10-01', creditsUsed: 321 }]
- }
+ let(:expected_response) do
+ {
+ success: true,
+ usersUsage: {
+ totalUsersUsingCredits: 3,
+ totalUsersUsingPool: 2,
+ totalUsersUsingOverage: 1,
+ creditsUsed: 123.45,
+ dailyUsage: [{ date: '2025-10-01', creditsUsed: 321 }]
}
- end
+ }
+ end
- include_context 'for self-managed request' do
- let(:variables) { { licenseKey: license_key, startDate: start_date, endDate: end_date } }
- end
+ include_context 'for self-managed request' do
+ let(:variables) { { licenseKey: license_key, startDate: start_date, endDate: end_date } }
+ end
- include_context 'for gitlab.com request' do
- let(:variables) { { namespaceId: namespace_id, startDate: start_date, endDate: end_date } }
- end
+ include_context 'for gitlab.com request' do
+ let(:variables) { { namespaceId: namespace_id, startDate: start_date, endDate: end_date } }
end
end
end
diff --git a/ee/spec/lib/gitlab_subscriptions/subscription_usage_spec.rb b/ee/spec/lib/gitlab_subscriptions/subscription_usage_spec.rb
index 7fd1207ae79bf61430e85ef3e37ef0916c2afecb..9c09de78b13b2a2e849920a7f0e801e2a82e9566 100644
--- a/ee/spec/lib/gitlab_subscriptions/subscription_usage_spec.rb
+++ b/ee/spec/lib/gitlab_subscriptions/subscription_usage_spec.rb
@@ -66,16 +66,6 @@
it 'returns the start date' do
expect(start_date).to be("2025-10-01")
end
-
- context 'when License.current is nil' do
- before do
- allow(License).to receive(:current).and_return(nil)
- end
-
- it 'handles nil license gracefully' do
- expect { start_date }.not_to raise_error
- end
- end
end
end
@@ -138,16 +128,6 @@
it 'returns the end date' do
expect(end_date).to be("2025-10-31")
end
-
- context 'when License.current is nil' do
- before do
- allow(License).to receive(:current).and_return(nil)
- end
-
- it 'handles nil license gracefully' do
- expect { end_date }.not_to raise_error
- end
- end
end
end
@@ -210,21 +190,11 @@
it 'returns the end date' do
expect(purchase_credits_path).to be("/mock/path")
end
-
- context 'when License.current is nil' do
- before do
- allow(License).to receive(:current).and_return(nil)
- end
-
- it 'handles nil license gracefully' do
- expect { purchase_credits_path }.not_to raise_error
- end
- end
end
end
- describe '#last_updated' do
- subject(:last_updated) { subscription_usage.last_updated }
+ describe '#last_event_transaction_at' do
+ subject(:last_event_transaction_at) { subscription_usage.last_event_transaction_at }
before do
allow(subscription_usage_client).to receive(:get_metadata).and_return(client_response)
@@ -240,10 +210,12 @@
end
context 'when the client returns a successful response' do
- let(:client_response) { { success: true, subscriptionUsage: { lastUpdated: "2025-10-01T16:19:59Z" } } }
+ let(:client_response) do
+ { success: true, subscriptionUsage: { lastEventTransactionAt: "2025-10-01T16:19:59Z" } }
+ end
it 'returns the last updated time' do
- expect(last_updated).to be("2025-10-01T16:19:59Z")
+ expect(last_event_transaction_at).to be("2025-10-01T16:19:59Z")
end
end
@@ -251,15 +223,15 @@
let(:client_response) { { success: false } }
it 'returns nil' do
- expect(last_updated).to be_nil
+ expect(last_event_transaction_at).to be_nil
end
end
- context 'when the client response is missing lastUpdated' do
- let(:client_response) { { success: true, subscriptionUsage: { lastUpdated: nil } } }
+ context 'when the client response is missing lastEventTransactionAt' do
+ let(:client_response) { { success: true, subscriptionUsage: { lastEventTransactionAt: nil } } }
it 'returns nil' do
- expect(last_updated).to be_nil
+ expect(last_event_transaction_at).to be_nil
end
end
end
@@ -273,26 +245,119 @@
end
let(:license) { build_stubbed(:license) }
- let(:client_response) { { success: true, subscriptionUsage: { lastUpdated: "2025-10-01T16:19:59Z" } } }
+ let(:client_response) { { success: true, subscriptionUsage: { lastEventTransactionAt: "2025-10-01T16:19:59Z" } } }
before do
allow(License).to receive(:current).and_return(license)
end
it 'returns the last updated time' do
- expect(last_updated).to be("2025-10-01T16:19:59Z")
+ expect(last_event_transaction_at).to be("2025-10-01T16:19:59Z")
+ end
+ end
+ end
+
+ describe '#one_time_credits' do
+ subject(:one_time_credits) { subscription_usage.one_time_credits }
+
+ before do
+ allow(subscription_usage_client).to receive(:get_one_time_credits).and_return(client_response)
+ end
+
+ context 'when subscription_target is :namespace' do
+ let(:subscription_usage) do
+ described_class.new(
+ subscription_target: :namespace,
+ subscription_usage_client: subscription_usage_client,
+ namespace: group
+ )
end
- context 'when License.current is nil' do
- before do
- allow(License).to receive(:current).and_return(nil)
+ context 'when the client returns a successful response' do
+ let(:client_response) do
+ {
+ success: true,
+ oneTimeCredits: {
+ creditsUsed: 25.32,
+ totalCredits: 1000,
+ totalCreditsRemaining: 974.68
+ }
+ }
+ end
+
+ it 'returns a OneTimeCredits struct with correct data' do
+ expect(one_time_credits).to be_a(GitlabSubscriptions::SubscriptionUsage::OneTimeCredits)
+ expect(one_time_credits).to have_attributes(
+ credits_used: 25.32,
+ total_credits: 1000,
+ total_credits_remaining: 974.68,
+ declarative_policy_subject: subscription_usage
+ )
end
+ end
+
+ context 'when the client returns an unsuccessful response' do
+ let(:client_response) { { success: false } }
- it 'handles nil license gracefully' do
- expect { last_updated }.not_to raise_error
+ it 'returns nil' do
+ expect(one_time_credits).to be_nil
+ end
+ end
+
+ context 'when the client response is missing oneTimeCredits data' do
+ let(:client_response) do
+ {
+ success: true,
+ oneTimeCredits: nil
+ }
+ end
+
+ it 'returns a OneTimeCredits struct with no values' do
+ expect(one_time_credits).to be_a(GitlabSubscriptions::SubscriptionUsage::OneTimeCredits)
+ expect(one_time_credits).to have_attributes(
+ credits_used: nil,
+ total_credits: nil,
+ total_credits_remaining: nil,
+ declarative_policy_subject: subscription_usage
+ )
end
end
end
+
+ context 'when subscription_target is :instance' do
+ let(:subscription_usage) do
+ described_class.new(
+ subscription_target: :instance,
+ subscription_usage_client: subscription_usage_client
+ )
+ end
+
+ let(:license) { build_stubbed(:license) }
+ let(:client_response) do
+ {
+ success: true,
+ oneTimeCredits: {
+ creditsUsed: 123.99,
+ totalCredits: 2000,
+ totalCreditsRemaining: 1876.01
+ }
+ }
+ end
+
+ before do
+ allow(License).to receive(:current).and_return(license)
+ end
+
+ it 'returns a OneTimeCredits struct with correct data' do
+ expect(one_time_credits).to be_a(GitlabSubscriptions::SubscriptionUsage::OneTimeCredits)
+ expect(one_time_credits).to have_attributes(
+ credits_used: 123.99,
+ total_credits: 2000,
+ total_credits_remaining: 1876.01,
+ declarative_policy_subject: subscription_usage
+ )
+ end
+ end
end
describe '#pool_usage' do
@@ -409,16 +474,6 @@
declarative_policy_subject: subscription_usage
)
end
-
- context 'when License.current is nil' do
- before do
- allow(License).to receive(:current).and_return(nil)
- end
-
- it 'handles nil license gracefully' do
- expect { pool_usage }.not_to raise_error
- end
- end
end
end
@@ -536,16 +591,6 @@
declarative_policy_subject: subscription_usage
)
end
-
- context 'when License.current is nil' do
- before do
- allow(License).to receive(:current).and_return(nil)
- end
-
- it 'handles nil license gracefully' do
- expect { overage }.not_to raise_error
- end
- end
end
end
diff --git a/ee/spec/lib/gitlab_subscriptions/subscriptions_usage/user_usage_spec.rb b/ee/spec/lib/gitlab_subscriptions/subscriptions_usage/user_usage_spec.rb
index 927d5dbbf4e908bdfadc8c20681805e3d97498f1..bb6b0612d0653df2d1942f390e0e56209c870108 100644
--- a/ee/spec/lib/gitlab_subscriptions/subscriptions_usage/user_usage_spec.rb
+++ b/ee/spec/lib/gitlab_subscriptions/subscriptions_usage/user_usage_spec.rb
@@ -14,6 +14,7 @@
totalUsersUsingCredits: 3,
totalUsersUsingPool: 2,
totalUsersUsingOverage: 1,
+ creditsUsed: 123.45,
dailyUsage: [{ date: '2025-10-01', creditsUsed: 321 }]
}
}
@@ -158,6 +159,36 @@
end
end
+ describe "#credits_used" do
+ before do
+ allow(subscription_usage_client).to receive(:get_users_usage_stats).and_return(client_response)
+ end
+
+ context 'when the client returns a successful response' do
+ let(:client_response) { { success: true, usersUsage: { creditsUsed: 123.45 } } }
+
+ it 'returns the correct data' do
+ expect(user_usage.credits_used).to eq(123.45)
+ end
+ end
+
+ context 'when the client returns an unsuccessful response' do
+ let(:client_response) { { success: false } }
+
+ it 'returns nil' do
+ expect(user_usage.credits_used).to be_nil
+ end
+ end
+
+ context 'when the client response is missing the data' do
+ let(:client_response) { { success: true, usersUsage: nil } }
+
+ it 'returns nil' do
+ expect(user_usage.credits_used).to be_nil
+ end
+ end
+ end
+
describe "#users" do
context 'when subscription_target is :namespace' do
before do
diff --git a/ee/spec/requests/api/graphql/gitlab_subscriptions/subscription_usage_spec.rb b/ee/spec/requests/api/graphql/gitlab_subscriptions/subscription_usage_spec.rb
index c2f4eb61d81207ce0311d1ae5295554dd18dc0b1..96042b2b834a7b44d6012b6531c8a1dc32c28353 100644
--- a/ee/spec/requests/api/graphql/gitlab_subscriptions/subscription_usage_spec.rb
+++ b/ee/spec/requests/api/graphql/gitlab_subscriptions/subscription_usage_spec.rb
@@ -49,9 +49,14 @@
let(:user_arguments) { {} }
let(:query_fields) do
[
- :last_updated,
+ :last_event_transaction_at,
:start_date,
:end_date,
+ query_graphql_field(:one_time_credits, {}, [
+ :credits_used,
+ :total_credits,
+ :total_credits_remaining
+ ]),
:purchase_credits_path,
query_graphql_field(:pool_usage, {}, [
:total_credits,
@@ -67,6 +72,7 @@
:total_users_using_credits,
:total_users_using_pool,
:total_users_using_overage,
+ :credits_used,
query_graphql_field(:daily_usage, {}, [:date, :credits_used]),
query_graphql_field(:users, user_arguments, [
query_graphql_field(:nodes, {}, [
@@ -124,7 +130,7 @@
subscriptionUsage: {
startDate: "2025-10-01",
endDate: "2025-10-31",
- lastUpdated: "2025-10-01T16:19:59Z",
+ lastEventTransactionAt: "2025-10-01T16:19:59Z",
purchaseCreditsPath: '/mock/path'
}
}
@@ -176,10 +182,20 @@
totalUsersUsingCredits: 3,
totalUsersUsingPool: 2,
totalUsersUsingOverage: 1,
+ creditsUsed: 123.45,
dailyUsage: [{ date: '2025-10-01', creditsUsed: 321 }]
}
}
+ one_time_credits = {
+ success: true,
+ oneTimeCredits: {
+ creditsUsed: 15.32,
+ totalCredits: 1000,
+ totalCreditsRemaining: 984.68
+ }
+ }
+
pool_usage = {
success: true,
poolUsage: {
@@ -201,6 +217,7 @@
allow_next_instance_of(Gitlab::SubscriptionPortal::SubscriptionUsageClient) do |client|
allow(client).to receive_messages(
get_metadata: metadata,
+ get_one_time_credits: one_time_credits,
get_pool_usage: pool_usage,
get_overage_usage: overage_usage,
get_events_for_user_id: { success: true, userEvents: events_for_user_id },
@@ -220,11 +237,17 @@
end
it 'returns subscription usage for instance' do
- expect(graphql_data_at(:subscription_usage, :lastUpdated)).to eq("2025-10-01T16:19:59Z")
+ expect(graphql_data_at(:subscription_usage, :lastEventTransactionAt)).to eq("2025-10-01T16:19:59Z")
expect(graphql_data_at(:subscription_usage, :startDate)).to eq("2025-10-01")
expect(graphql_data_at(:subscription_usage, :endDate)).to eq("2025-10-31")
expect(graphql_data_at(:subscription_usage, :purchaseCreditsPath)).to eq("/mock/path")
+ expect(graphql_data_at(:subscription_usage, :oneTimeCredits)).to eq({
+ creditsUsed: 15.32,
+ totalCredits: 1000,
+ totalCreditsRemaining: 984.68
+ }.with_indifferent_access)
+
expect(graphql_data_at(:subscription_usage, :poolUsage)).to eq({
totalCredits: 1000,
creditsUsed: 250,
@@ -240,6 +263,7 @@
expect(graphql_data_at(:subscription_usage, :usersUsage, :totalUsersUsingCredits)).to eq(3)
expect(graphql_data_at(:subscription_usage, :usersUsage, :totalUsersUsingPool)).to eq(2)
expect(graphql_data_at(:subscription_usage, :usersUsage, :totalUsersUsingOverage)).to eq(1)
+ expect(graphql_data_at(:subscription_usage, :usersUsage, :creditsUsed)).to eq(123.45)
expect(graphql_data_at(:subscription_usage, :usersUsage, :dailyUsage))
.to match_array([{ date: '2025-10-01', creditsUsed: 321 }.with_indifferent_access])
@@ -322,11 +346,17 @@
end
it 'returns subscription usage for the group' do
- expect(graphql_data_at(:subscription_usage, :lastUpdated)).to eq("2025-10-01T16:19:59Z")
+ expect(graphql_data_at(:subscription_usage, :lastEventTransactionAt)).to eq("2025-10-01T16:19:59Z")
expect(graphql_data_at(:subscription_usage, :startDate)).to eq("2025-10-01")
expect(graphql_data_at(:subscription_usage, :endDate)).to eq("2025-10-31")
expect(graphql_data_at(:subscription_usage, :purchaseCreditsPath)).to eq("/mock/path")
+ expect(graphql_data_at(:subscription_usage, :oneTimeCredits)).to eq({
+ creditsUsed: 15.32,
+ totalCredits: 1000,
+ totalCreditsRemaining: 984.68
+ }.with_indifferent_access)
+
expect(graphql_data_at(:subscription_usage, :poolUsage)).to eq({
totalCredits: 1000,
creditsUsed: 250,
@@ -342,6 +372,7 @@
expect(graphql_data_at(:subscription_usage, :usersUsage, :totalUsersUsingCredits)).to eq(3)
expect(graphql_data_at(:subscription_usage, :usersUsage, :totalUsersUsingPool)).to eq(2)
expect(graphql_data_at(:subscription_usage, :usersUsage, :totalUsersUsingOverage)).to eq(1)
+ expect(graphql_data_at(:subscription_usage, :usersUsage, :creditsUsed)).to eq(123.45)
expect(graphql_data_at(:subscription_usage, :usersUsage, :dailyUsage))
.to match_array([{ date: '2025-10-01', creditsUsed: 321 }.with_indifferent_access])